@neat.is/core 0.7.0 → 0.7.1

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 path60 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path60) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path60)) {
64
+ const path61 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path61) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path61)) {
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_path37.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
198
- return import_node_path37.default.resolve(here, "..", "proto");
197
+ const here = import_node_path38.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
198
+ return import_node_path38.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_path37, import_node_crypto2, grpc, protoLoader;
266
+ var import_node_url, import_node_path38, 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_path37 = __toESM(require("path"), 1);
272
+ import_node_path38 = __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);
@@ -355,6 +355,46 @@ function tableFromSqlStatement(sql) {
355
355
  const m = /\b(?:from|into|update)\s+(?:"?[\w$]+"?\s*\.\s*)?"?([a-zA-Z_][\w$]*)"?/i.exec(sql);
356
356
  return m ? m[1] : null;
357
357
  }
358
+ function columnsFromSqlStatement(sql) {
359
+ if (typeof sql !== "string" || sql.length === 0) return [];
360
+ const s = sql.replace(/\s+/g, " ").trim();
361
+ if (/\bjoin\b/i.test(s)) return [];
362
+ if ((s.match(/\bfrom\b/gi) ?? []).length > 1) return [];
363
+ const bare = (raw) => {
364
+ let t = raw.trim().replace(/"/g, "");
365
+ if (/[()*]/.test(t)) return null;
366
+ t = t.split(/\s+as\s+/i)[0].trim();
367
+ t = t.split(".").pop();
368
+ return /^[a-z_][\w$]*$/i.test(t) ? t.toLowerCase() : null;
369
+ };
370
+ const isCol = (c) => c !== null;
371
+ const cols = (list) => [...new Set(list.split(",").map(bare).filter(isCol))];
372
+ const whereCols = (w) => w ? [
373
+ ...new Set(
374
+ [
375
+ ...w.matchAll(
376
+ /(?:"?[\w$]+"?\.)?"?([a-z_][\w$]*)"?\s*(?:=|<|>|<=|>=|<>|!=|\bis\b|\bin\b|\blike\b)/gi
377
+ )
378
+ ].map((match) => match[1].toLowerCase()).filter((c) => !/^(and|or|not|null)$/i.test(c))
379
+ )
380
+ ] : [];
381
+ let m;
382
+ if (m = /\binsert\s+into\s+(?:"?[\w$]+"?\.)?"?[\w$]+"?\s*\(([^)]*)\)/i.exec(s)) {
383
+ return cols(m[1]);
384
+ }
385
+ if (m = /\bupdate\s+(?:"?[\w$]+"?\.)?"?[\w$]+"?\s+set\s+(.+?)(?:\bwhere\b(.+))?$/i.exec(s)) {
386
+ const set = m[1].split(",").map((p) => bare(p.split("=")[0])).filter(isCol);
387
+ return [.../* @__PURE__ */ new Set([...set, ...whereCols(m[2] ?? void 0)])];
388
+ }
389
+ if (m = /\bdelete\s+from\s+(?:"?[\w$]+"?\.)?"?[\w$]+"?(?:\s+where\b(.+))?$/i.exec(s)) {
390
+ return whereCols(m[1] ?? void 0);
391
+ }
392
+ if (m = /\bselect\s+(.+?)\s+from\s+(?:"?[\w$]+"?\.)?"?[\w$]+"?(?:\s+where\b(.+))?$/i.exec(s)) {
393
+ if (m[1].trim() === "*") return [];
394
+ return [.../* @__PURE__ */ new Set([...cols(m[1]), ...whereCols(m[2] ?? void 0)])];
395
+ }
396
+ return [];
397
+ }
358
398
  function messagingDestinationOf(attrs) {
359
399
  for (const key of ["messaging.destination.name", "messaging.destination"]) {
360
400
  const v = attrs[key];
@@ -375,8 +415,8 @@ function websocketChannelPathOf(attrs) {
375
415
  const v = attrs[key];
376
416
  if (typeof v === "string" && v.length > 0) {
377
417
  const q = v.indexOf("?");
378
- const path60 = q === -1 ? v : v.slice(0, q);
379
- if (path60.length > 0) return path60;
418
+ const path61 = q === -1 ? v : v.slice(0, q);
419
+ if (path61.length > 0) return path61;
380
420
  }
381
421
  }
382
422
  return void 0;
@@ -413,6 +453,7 @@ function parseOtlpRequest(body) {
413
453
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
414
454
  dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
415
455
  dbTable: typeof attrs["db.statement"] === "string" ? tableFromSqlStatement(attrs["db.statement"]) ?? void 0 : void 0,
456
+ dbColumns: typeof attrs["db.statement"] === "string" ? columnsFromSqlStatement(attrs["db.statement"]) : void 0,
416
457
  httpRoute: typeof attrs["http.route"] === "string" ? attrs["http.route"] : void 0,
417
458
  httpMethod: typeof attrs["http.request.method"] === "string" ? attrs["http.request.method"] : typeof attrs["http.method"] === "string" ? attrs["http.method"] : void 0,
418
459
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
@@ -434,10 +475,10 @@ function parseOtlpRequest(body) {
434
475
  return out;
435
476
  }
436
477
  function loadProtoRoot() {
437
- const here = import_node_path38.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
438
- const protoRoot = import_node_path38.default.resolve(here, "..", "proto");
478
+ const here = import_node_path39.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
479
+ const protoRoot = import_node_path39.default.resolve(here, "..", "proto");
439
480
  const root = new import_protobufjs.default.Root();
440
- root.resolvePath = (_origin, target) => import_node_path38.default.resolve(protoRoot, target);
481
+ root.resolvePath = (_origin, target) => import_node_path39.default.resolve(protoRoot, target);
441
482
  root.loadSync(
442
483
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
443
484
  { keepCase: true }
@@ -668,12 +709,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
668
709
  }
669
710
  }
670
711
  }
671
- var import_node_path38, import_node_url2, import_fastify, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
712
+ var import_node_path39, import_node_url2, import_fastify, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
672
713
  var init_otel = __esm({
673
714
  "src/otel.ts"() {
674
715
  "use strict";
675
716
  init_cjs_shims();
676
- import_node_path38 = __toESM(require("path"), 1);
717
+ import_node_path39 = __toESM(require("path"), 1);
677
718
  import_node_url2 = require("url");
678
719
  import_fastify = __toESM(require("fastify"), 1);
679
720
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -697,13 +738,13 @@ __export(neatd_exports, {
697
738
  module.exports = __toCommonJS(neatd_exports);
698
739
  init_cjs_shims();
699
740
  var import_node_fs34 = require("fs");
700
- var import_node_path59 = __toESM(require("path"), 1);
741
+ var import_node_path60 = __toESM(require("path"), 1);
701
742
  var import_node_module2 = require("module");
702
743
 
703
744
  // src/daemon.ts
704
745
  init_cjs_shims();
705
746
  var import_node_fs32 = require("fs");
706
- var import_node_path57 = __toESM(require("path"), 1);
747
+ var import_node_path58 = __toESM(require("path"), 1);
707
748
  var import_node_module = require("module");
708
749
 
709
750
  // src/graph.ts
@@ -1236,19 +1277,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1236
1277
  function longestIncomingWalk(graph, start, maxDepth) {
1237
1278
  let best = { path: [start], edges: [] };
1238
1279
  const visited = /* @__PURE__ */ new Set([start]);
1239
- function step(node, path60, edges) {
1240
- if (path60.length > best.path.length) {
1241
- best = { path: [...path60], edges: [...edges] };
1280
+ function step(node, path61, edges) {
1281
+ if (path61.length > best.path.length) {
1282
+ best = { path: [...path61], edges: [...edges] };
1242
1283
  }
1243
- if (path60.length - 1 >= maxDepth) return;
1284
+ if (path61.length - 1 >= maxDepth) return;
1244
1285
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1245
1286
  for (const [srcId, edge] of incoming) {
1246
1287
  if (visited.has(srcId)) continue;
1247
1288
  visited.add(srcId);
1248
- path60.push(srcId);
1289
+ path61.push(srcId);
1249
1290
  edges.push(edge);
1250
- step(srcId, path60, edges);
1251
- path60.pop();
1291
+ step(srcId, path61, edges);
1292
+ path61.pop();
1252
1293
  edges.pop();
1253
1294
  visited.delete(srcId);
1254
1295
  }
@@ -1455,26 +1496,26 @@ function dominantFailingCall(graph, serviceId7, visited) {
1455
1496
  return best;
1456
1497
  }
1457
1498
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1458
- const path60 = [originServiceId];
1499
+ const path61 = [originServiceId];
1459
1500
  const edges = [];
1460
1501
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1461
1502
  let current = originServiceId;
1462
1503
  for (let depth = 0; depth < maxDepth; depth++) {
1463
1504
  const hop = dominantFailingCall(graph, current, visited);
1464
1505
  if (!hop) break;
1465
- path60.push(hop.nextService);
1506
+ path61.push(hop.nextService);
1466
1507
  edges.push(hop.edge);
1467
1508
  visited.add(hop.nextService);
1468
1509
  current = hop.nextService;
1469
1510
  }
1470
1511
  if (edges.length === 0) return null;
1471
- return { path: path60, edges, culprit: current };
1512
+ return { path: path61, edges, culprit: current };
1472
1513
  }
1473
1514
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1474
1515
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1475
1516
  if (!chain) return null;
1476
1517
  const culprit = chain.culprit;
1477
- const path60 = [...chain.path];
1518
+ const path61 = [...chain.path];
1478
1519
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1479
1520
  const baseConfidence = confidenceFromMix(chain.edges);
1480
1521
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1482,14 +1523,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1482
1523
  if (loc) {
1483
1524
  let rootCauseNode = culprit;
1484
1525
  if (loc.fileNode) {
1485
- path60.push(loc.fileNode);
1526
+ path61.push(loc.fileNode);
1486
1527
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1487
1528
  rootCauseNode = loc.fileNode;
1488
1529
  }
1489
1530
  return import_types.RootCauseResultSchema.parse({
1490
1531
  rootCauseNode,
1491
1532
  rootCauseReason: loc.rootCauseReason,
1492
- traversalPath: path60,
1533
+ traversalPath: path61,
1493
1534
  edgeProvenances,
1494
1535
  confidence,
1495
1536
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1501,7 +1542,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1501
1542
  return import_types.RootCauseResultSchema.parse({
1502
1543
  rootCauseNode: culprit,
1503
1544
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1504
- traversalPath: path60,
1545
+ traversalPath: path61,
1505
1546
  edgeProvenances,
1506
1547
  confidence,
1507
1548
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2089,7 +2130,7 @@ var PolicyViolationsLog = class {
2089
2130
  };
2090
2131
 
2091
2132
  // src/ingest.ts
2092
- var import_types6 = require("@neat.is/types");
2133
+ var import_types7 = require("@neat.is/types");
2093
2134
 
2094
2135
  // src/extract/routes.ts
2095
2136
  init_cjs_shims();
@@ -3135,6 +3176,42 @@ async function addRoutes(graph, services) {
3135
3176
  return { nodesAdded, edgesAdded };
3136
3177
  }
3137
3178
 
3179
+ // src/columns.ts
3180
+ init_cjs_shims();
3181
+ var import_types6 = require("@neat.is/types");
3182
+ var OBSERVED_COLUMN_CONFIDENCE = 0.9;
3183
+ function normalizeProvenances(provenances) {
3184
+ return [...new Set(provenances)].sort();
3185
+ }
3186
+ function foldColumns(existing, names, provenance, confidence) {
3187
+ const out = (existing ?? []).map((c) => ({
3188
+ ...c,
3189
+ provenances: [...c.provenances]
3190
+ }));
3191
+ const byName = new Map(out.map((c) => [c.name, c]));
3192
+ for (const raw of names) {
3193
+ const name = raw.toLowerCase();
3194
+ const prior = byName.get(name);
3195
+ if (!prior) {
3196
+ const col = { name, provenances: [provenance], confidence };
3197
+ byName.set(name, col);
3198
+ out.push(col);
3199
+ continue;
3200
+ }
3201
+ if (!prior.provenances.includes(provenance)) {
3202
+ prior.provenances = normalizeProvenances([...prior.provenances, provenance]);
3203
+ }
3204
+ if (confidence > prior.confidence) prior.confidence = confidence;
3205
+ }
3206
+ return out;
3207
+ }
3208
+ function columnIsDeclared(col) {
3209
+ return col.provenances.includes(import_types6.Provenance.EXTRACTED);
3210
+ }
3211
+ function columnIsObserved(col) {
3212
+ return col.provenances.includes(import_types6.Provenance.OBSERVED);
3213
+ }
3214
+
3138
3215
  // src/ingest.ts
3139
3216
  var HOUR_MS = 60 * 60 * 1e3;
3140
3217
  var DAY_MS = 24 * HOUR_MS;
@@ -3434,11 +3511,11 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
3434
3511
  };
3435
3512
  }
3436
3513
  function reconcileObservedRelPath(graph, serviceName, relPath) {
3437
- if (graph.hasNode((0, import_types6.fileId)(serviceName, relPath))) return relPath;
3514
+ if (graph.hasNode((0, import_types7.fileId)(serviceName, relPath))) return relPath;
3438
3515
  let best = null;
3439
3516
  graph.forEachNode((_id, attrs) => {
3440
3517
  const a = attrs;
3441
- if (a.type !== import_types6.NodeType.FileNode || a.service !== serviceName) return;
3518
+ if (a.type !== import_types7.NodeType.FileNode || a.service !== serviceName) return;
3442
3519
  if (a.discoveredVia === "otel") return;
3443
3520
  const p = a.path;
3444
3521
  if (!p) return;
@@ -3450,14 +3527,14 @@ function reconcileObservedRelPath(graph, serviceName, relPath) {
3450
3527
  }
3451
3528
  function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
3452
3529
  const svcAttrs = graph.hasNode(serviceNodeId) ? graph.getNodeAttributes(serviceNodeId) : void 0;
3453
- const canonicalService = svcAttrs && svcAttrs.type === import_types6.NodeType.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
3530
+ const canonicalService = svcAttrs && svcAttrs.type === import_types7.NodeType.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
3454
3531
  const relPath = reconcileObservedRelPath(graph, canonicalService, callSite.relPath);
3455
- const fileNodeId = (0, import_types6.fileId)(canonicalService, relPath);
3532
+ const fileNodeId = (0, import_types7.fileId)(canonicalService, relPath);
3456
3533
  if (!graph.hasNode(fileNodeId)) {
3457
3534
  const language = languageForExt(relPath);
3458
3535
  const node = {
3459
3536
  id: fileNodeId,
3460
- type: import_types6.NodeType.FileNode,
3537
+ type: import_types7.NodeType.FileNode,
3461
3538
  service: canonicalService,
3462
3539
  path: relPath,
3463
3540
  ...language ? { language } : {},
@@ -3466,14 +3543,14 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
3466
3543
  };
3467
3544
  graph.addNode(fileNodeId, node);
3468
3545
  }
3469
- const containsId = makeObservedEdgeId(import_types6.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
3546
+ const containsId = makeObservedEdgeId(import_types7.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
3470
3547
  if (!graph.hasEdge(containsId)) {
3471
3548
  const edge = {
3472
3549
  id: containsId,
3473
3550
  source: serviceNodeId,
3474
3551
  target: fileNodeId,
3475
- type: import_types6.EdgeType.CONTAINS,
3476
- provenance: import_types6.Provenance.OBSERVED
3552
+ type: import_types7.EdgeType.CONTAINS,
3553
+ provenance: import_types7.Provenance.OBSERVED
3477
3554
  };
3478
3555
  graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
3479
3556
  }
@@ -3497,11 +3574,11 @@ function pickContainingSymbol(candidates, fn) {
3497
3574
  return [...candidates].sort(bySpan)[0].id;
3498
3575
  }
3499
3576
  function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line) {
3500
- const sid = (0, import_types6.symbolId)(service, relPath, fn);
3577
+ const sid = (0, import_types7.symbolId)(service, relPath, fn);
3501
3578
  if (!graph.hasNode(sid)) {
3502
3579
  const node = {
3503
3580
  id: sid,
3504
- type: import_types6.NodeType.SymbolNode,
3581
+ type: import_types7.NodeType.SymbolNode,
3505
3582
  kind: "function",
3506
3583
  qualname: fn,
3507
3584
  span: { startLine: line, endLine: line },
@@ -3511,14 +3588,14 @@ function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line)
3511
3588
  };
3512
3589
  graph.addNode(sid, node);
3513
3590
  }
3514
- const containsId = makeObservedEdgeId(import_types6.EdgeType.CONTAINS, fileNodeId, sid);
3591
+ const containsId = makeObservedEdgeId(import_types7.EdgeType.CONTAINS, fileNodeId, sid);
3515
3592
  if (!graph.hasEdge(containsId)) {
3516
3593
  const edge = {
3517
3594
  id: containsId,
3518
3595
  source: fileNodeId,
3519
3596
  target: sid,
3520
- type: import_types6.EdgeType.CONTAINS,
3521
- provenance: import_types6.Provenance.OBSERVED
3597
+ type: import_types7.EdgeType.CONTAINS,
3598
+ provenance: import_types7.Provenance.OBSERVED
3522
3599
  };
3523
3600
  graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
3524
3601
  }
@@ -3530,9 +3607,9 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
3530
3607
  let sawSymbol = false;
3531
3608
  const candidates = [];
3532
3609
  graph.forEachOutboundEdge(fileNodeId, (_edge, edgeAttrs, _source, target) => {
3533
- if (edgeAttrs.type !== import_types6.EdgeType.CONTAINS) return;
3610
+ if (edgeAttrs.type !== import_types7.EdgeType.CONTAINS) return;
3534
3611
  const t = graph.getNodeAttributes(target);
3535
- if (t.type !== import_types6.NodeType.SymbolNode) return;
3612
+ if (t.type !== import_types7.NodeType.SymbolNode) return;
3536
3613
  sawSymbol = true;
3537
3614
  if (line >= t.span.startLine && line <= t.span.endLine) {
3538
3615
  candidates.push({ id: target, symbol: t });
@@ -3545,17 +3622,17 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
3545
3622
  return fileNodeId;
3546
3623
  }
3547
3624
  function makeObservedEdgeId(type, source, target) {
3548
- return (0, import_types6.observedEdgeId)(source, target, type);
3625
+ return (0, import_types7.observedEdgeId)(source, target, type);
3549
3626
  }
3550
3627
  function makeInferredEdgeId(type, source, target) {
3551
- return (0, import_types6.inferredEdgeId)(source, target, type);
3628
+ return (0, import_types7.inferredEdgeId)(source, target, type);
3552
3629
  }
3553
3630
  var INFERRED_CONFIDENCE = 0.6;
3554
3631
  var STITCH_MAX_DEPTH = 2;
3555
3632
  var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
3556
- import_types6.EdgeType.CALLS,
3557
- import_types6.EdgeType.CONNECTS_TO,
3558
- import_types6.EdgeType.DEPENDS_ON
3633
+ import_types7.EdgeType.CALLS,
3634
+ import_types7.EdgeType.CONNECTS_TO,
3635
+ import_types7.EdgeType.DEPENDS_ON
3559
3636
  ]);
3560
3637
  var WIRE_SPAN_KIND_CLIENT = 3;
3561
3638
  var WIRE_SPAN_KIND_PRODUCER = 4;
@@ -3571,11 +3648,11 @@ function spanServesGraphqlOperation(kind) {
3571
3648
  return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
3572
3649
  }
3573
3650
  function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
3574
- const id = (0, import_types6.graphqlOperationId)(serviceName, operationType, operationName);
3651
+ const id = (0, import_types7.graphqlOperationId)(serviceName, operationType, operationName);
3575
3652
  if (graph.hasNode(id)) return id;
3576
3653
  const node = {
3577
3654
  id,
3578
- type: import_types6.NodeType.GraphQLOperationNode,
3655
+ type: import_types7.NodeType.GraphQLOperationNode,
3579
3656
  name: operationName,
3580
3657
  service: serviceName,
3581
3658
  operationType: operationType.toLowerCase(),
@@ -3589,11 +3666,11 @@ function spanServesGrpcMethod(kind) {
3589
3666
  return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
3590
3667
  }
3591
3668
  function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
3592
- const id = (0, import_types6.grpcMethodId)(rpcService, rpcMethod);
3669
+ const id = (0, import_types7.grpcMethodId)(rpcService, rpcMethod);
3593
3670
  if (graph.hasNode(id)) return id;
3594
3671
  const node = {
3595
3672
  id,
3596
- type: import_types6.NodeType.GrpcMethodNode,
3673
+ type: import_types7.NodeType.GrpcMethodNode,
3597
3674
  name: `${rpcService}/${rpcMethod}`,
3598
3675
  rpcService,
3599
3676
  rpcMethod,
@@ -3606,11 +3683,11 @@ function spanServesWebsocketChannel(kind) {
3606
3683
  return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
3607
3684
  }
3608
3685
  function ensureWebsocketChannelNode(graph, serviceName, channel) {
3609
- const id = (0, import_types6.websocketChannelId)(serviceName, channel);
3686
+ const id = (0, import_types7.websocketChannelId)(serviceName, channel);
3610
3687
  if (graph.hasNode(id)) return id;
3611
3688
  const node = {
3612
3689
  id,
3613
- type: import_types6.NodeType.WebSocketChannelNode,
3690
+ type: import_types7.NodeType.WebSocketChannelNode,
3614
3691
  name: channel,
3615
3692
  service: serviceName,
3616
3693
  channel,
@@ -3623,11 +3700,11 @@ function messagingDestinationKind(system) {
3623
3700
  return `${system}-topic`;
3624
3701
  }
3625
3702
  function ensureMessagingDestinationNode(graph, system, destination) {
3626
- const id = (0, import_types6.infraId)(messagingDestinationKind(system), destination);
3703
+ const id = (0, import_types7.infraId)(messagingDestinationKind(system), destination);
3627
3704
  if (graph.hasNode(id)) return id;
3628
3705
  const node = {
3629
3706
  id,
3630
- type: import_types6.NodeType.InfraNode,
3707
+ type: import_types7.NodeType.InfraNode,
3631
3708
  name: destination,
3632
3709
  provider: "self",
3633
3710
  kind: messagingDestinationKind(system)
@@ -3671,9 +3748,9 @@ function lookupParentSpan(traceId, parentSpanId, now) {
3671
3748
  };
3672
3749
  }
3673
3750
  function resolveServiceId(graph, host, env) {
3674
- const envTagged = (0, import_types6.serviceId)(host, env);
3751
+ const envTagged = (0, import_types7.serviceId)(host, env);
3675
3752
  if (graph.hasNode(envTagged)) return envTagged;
3676
- const envLess = (0, import_types6.serviceId)(host);
3753
+ const envLess = (0, import_types7.serviceId)(host);
3677
3754
  if (envLess !== envTagged && graph.hasNode(envLess)) return envLess;
3678
3755
  let sameEnv = null;
3679
3756
  let envLessMatch = null;
@@ -3681,7 +3758,7 @@ function resolveServiceId(graph, host, env) {
3681
3758
  graph.forEachNode((id, attrs) => {
3682
3759
  if (sameEnv) return;
3683
3760
  const a = attrs;
3684
- if (a.type !== import_types6.NodeType.ServiceNode) return;
3761
+ if (a.type !== import_types7.NodeType.ServiceNode) return;
3685
3762
  const matchesByName = a.name === host;
3686
3763
  const matchesByAlias = a.aliases ? a.aliases.includes(host) : false;
3687
3764
  if (!matchesByName && !matchesByAlias) return;
@@ -3696,14 +3773,14 @@ function resolveServiceId(graph, host, env) {
3696
3773
  return sameEnv ?? envLessMatch ?? anyMatch;
3697
3774
  }
3698
3775
  function frontierIdFor(host) {
3699
- return (0, import_types6.frontierId)(host);
3776
+ return (0, import_types7.frontierId)(host);
3700
3777
  }
3701
3778
  function ensureServiceNode(graph, serviceName, env) {
3702
- const id = (0, import_types6.serviceId)(serviceName, env);
3779
+ const id = (0, import_types7.serviceId)(serviceName, env);
3703
3780
  if (graph.hasNode(id)) return id;
3704
3781
  const wanted = serviceName.toLowerCase();
3705
3782
  const extractedId = graph.findNode((_nid, attrs) => {
3706
- if (attrs.type !== import_types6.NodeType.ServiceNode) return false;
3783
+ if (attrs.type !== import_types7.NodeType.ServiceNode) return false;
3707
3784
  const svc = attrs;
3708
3785
  if (svc.discoveredVia === "otel") return false;
3709
3786
  return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
@@ -3711,7 +3788,7 @@ function ensureServiceNode(graph, serviceName, env) {
3711
3788
  if (extractedId) return extractedId;
3712
3789
  const node = {
3713
3790
  id,
3714
- type: import_types6.NodeType.ServiceNode,
3791
+ type: import_types7.NodeType.ServiceNode,
3715
3792
  name: serviceName,
3716
3793
  language: "unknown",
3717
3794
  discoveredVia: "otel",
@@ -3721,11 +3798,11 @@ function ensureServiceNode(graph, serviceName, env) {
3721
3798
  return id;
3722
3799
  }
3723
3800
  function ensureInfraNode(graph, kind, name, provider) {
3724
- const id = (0, import_types6.infraId)(kind, name);
3801
+ const id = (0, import_types7.infraId)(kind, name);
3725
3802
  if (graph.hasNode(id)) return id;
3726
3803
  const node = {
3727
3804
  id,
3728
- type: import_types6.NodeType.InfraNode,
3805
+ type: import_types7.NodeType.InfraNode,
3729
3806
  name,
3730
3807
  provider,
3731
3808
  kind
@@ -3733,12 +3810,27 @@ function ensureInfraNode(graph, kind, name, provider) {
3733
3810
  graph.addNode(id, node);
3734
3811
  return id;
3735
3812
  }
3813
+ var COLUMN_BEARING_INFRA_KINDS = /* @__PURE__ */ new Set(["sql-table", "supabase-table"]);
3814
+ function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
3815
+ if (!columns || columns.length === 0 || !graph.hasNode(tableNodeId)) return;
3816
+ const node = graph.getNodeAttributes(tableNodeId);
3817
+ if (node.type !== import_types7.NodeType.InfraNode || !node.kind || !COLUMN_BEARING_INFRA_KINDS.has(node.kind)) {
3818
+ return;
3819
+ }
3820
+ graph.replaceNodeAttributes(tableNodeId, {
3821
+ ...node,
3822
+ columns: foldColumns(node.columns, columns, provenance, confidence)
3823
+ });
3824
+ }
3825
+ function mergeObservedColumns(graph, tableNodeId, columns) {
3826
+ mergeColumnsAt(graph, tableNodeId, columns, import_types7.Provenance.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
3827
+ }
3736
3828
  function ensureDatabaseNode(graph, host, engine) {
3737
- const id = (0, import_types6.databaseId)(host);
3829
+ const id = (0, import_types7.databaseId)(host);
3738
3830
  if (graph.hasNode(id)) return id;
3739
3831
  const node = {
3740
3832
  id,
3741
- type: import_types6.NodeType.DatabaseNode,
3833
+ type: import_types7.NodeType.DatabaseNode,
3742
3834
  name: host,
3743
3835
  engine,
3744
3836
  engineVersion: "unknown",
@@ -3750,11 +3842,11 @@ function ensureDatabaseNode(graph, host, engine) {
3750
3842
  return id;
3751
3843
  }
3752
3844
  function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
3753
- const id = (0, import_types6.localDatabaseId)(serviceName, name);
3845
+ const id = (0, import_types7.localDatabaseId)(serviceName, name);
3754
3846
  if (graph.hasNode(id)) return id;
3755
3847
  const node = {
3756
3848
  id,
3757
- type: import_types6.NodeType.DatabaseNode,
3849
+ type: import_types7.NodeType.DatabaseNode,
3758
3850
  name,
3759
3851
  engine,
3760
3852
  engineVersion: "unknown",
@@ -3769,17 +3861,17 @@ function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
3769
3861
  const sources = [serviceNodeId];
3770
3862
  for (const edgeId of graph.outboundEdges(serviceNodeId)) {
3771
3863
  const e = graph.getEdgeAttributes(edgeId);
3772
- if (e.type === import_types6.EdgeType.CONTAINS) sources.push(e.target);
3864
+ if (e.type === import_types7.EdgeType.CONTAINS) sources.push(e.target);
3773
3865
  }
3774
3866
  const matches = /* @__PURE__ */ new Set();
3775
3867
  for (const src of sources) {
3776
3868
  if (!graph.hasNode(src)) continue;
3777
3869
  for (const edgeId of graph.outboundEdges(src)) {
3778
3870
  const edge = graph.getEdgeAttributes(edgeId);
3779
- if (edge.type !== import_types6.EdgeType.CONNECTS_TO || edge.provenance !== import_types6.Provenance.EXTRACTED) continue;
3871
+ if (edge.type !== import_types7.EdgeType.CONNECTS_TO || edge.provenance !== import_types7.Provenance.EXTRACTED) continue;
3780
3872
  if (!graph.hasNode(edge.target)) continue;
3781
3873
  const target = graph.getNodeAttributes(edge.target);
3782
- if (target.type !== import_types6.NodeType.DatabaseNode || target.engine !== engine) continue;
3874
+ if (target.type !== import_types7.NodeType.DatabaseNode || target.engine !== engine) continue;
3783
3875
  matches.add(edge.target);
3784
3876
  }
3785
3877
  }
@@ -3794,7 +3886,7 @@ function ensureFrontierNode(graph, host, ts) {
3794
3886
  }
3795
3887
  const node = {
3796
3888
  id,
3797
- type: import_types6.NodeType.FrontierNode,
3889
+ type: import_types7.NodeType.FrontierNode,
3798
3890
  name: host,
3799
3891
  host,
3800
3892
  firstObserved: ts,
@@ -3818,11 +3910,11 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
3818
3910
  };
3819
3911
  const updated = {
3820
3912
  ...existing,
3821
- provenance: import_types6.Provenance.OBSERVED,
3913
+ provenance: import_types7.Provenance.OBSERVED,
3822
3914
  lastObserved: ts,
3823
3915
  callCount: newSpanCount,
3824
3916
  signal: newSignal,
3825
- confidence: (0, import_types6.confidenceForObservedSignal)(newSignal),
3917
+ confidence: (0, import_types7.confidenceForObservedSignal)(newSignal),
3826
3918
  grain
3827
3919
  // backfills legacy edges that predate ADR-142
3828
3920
  };
@@ -3839,8 +3931,8 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
3839
3931
  source,
3840
3932
  target,
3841
3933
  type,
3842
- provenance: import_types6.Provenance.OBSERVED,
3843
- confidence: (0, import_types6.confidenceForObservedSignal)(signal),
3934
+ provenance: import_types7.Provenance.OBSERVED,
3935
+ confidence: (0, import_types7.confidenceForObservedSignal)(signal),
3844
3936
  lastObserved: ts,
3845
3937
  callCount: 1,
3846
3938
  signal,
@@ -3862,9 +3954,9 @@ function stitchTrace(graph, sourceServiceId, ts) {
3862
3954
  const outbound = graph.outboundEdges(nodeId);
3863
3955
  for (const edgeId of outbound) {
3864
3956
  const edge = graph.getEdgeAttributes(edgeId);
3865
- if (edge.provenance !== import_types6.Provenance.EXTRACTED) continue;
3957
+ if (edge.provenance !== import_types7.Provenance.EXTRACTED) continue;
3866
3958
  if (!STITCH_EDGE_TYPES.has(edge.type)) continue;
3867
- if (graph.hasEdge((0, import_types6.observedEdgeId)(edge.source, edge.target, edge.type))) continue;
3959
+ if (graph.hasEdge((0, import_types7.observedEdgeId)(edge.source, edge.target, edge.type))) continue;
3868
3960
  upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts);
3869
3961
  if (!visited.has(edge.target)) {
3870
3962
  visited.add(edge.target);
@@ -3886,7 +3978,7 @@ function upsertInferredEdge(graph, type, source, target, ts) {
3886
3978
  source,
3887
3979
  target,
3888
3980
  type,
3889
- provenance: import_types6.Provenance.INFERRED,
3981
+ provenance: import_types7.Provenance.INFERRED,
3890
3982
  confidence: INFERRED_CONFIDENCE,
3891
3983
  lastObserved: ts
3892
3984
  };
@@ -3897,12 +3989,12 @@ async function appendErrorEvent(ctx, ev) {
3897
3989
  await import_node_fs6.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
3898
3990
  }
3899
3991
  function incidentAffectedNode(span, graph, scanPath) {
3900
- const sid = (0, import_types6.serviceId)(span.service, span.env);
3992
+ const sid = (0, import_types7.serviceId)(span.service, span.env);
3901
3993
  const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
3902
3994
  const callSite = callSiteFromSpan(span, serviceNode, scanPath);
3903
3995
  if (callSite) {
3904
3996
  const relPath = graph ? reconcileObservedRelPath(graph, span.service, callSite.relPath) : callSite.relPath;
3905
- return (0, import_types6.fileId)(span.service, relPath);
3997
+ return (0, import_types7.fileId)(span.service, relPath);
3906
3998
  }
3907
3999
  return sid;
3908
4000
  }
@@ -4027,7 +4119,7 @@ function findRouteNodeByHttpRoute(graph, serviceName, method, httpRoute) {
4027
4119
  graph.forEachNode((id, attrs) => {
4028
4120
  if (found) return;
4029
4121
  const a = attrs;
4030
- if (a.type !== import_types6.NodeType.RouteNode || a.service !== serviceName) return;
4122
+ if (a.type !== import_types7.NodeType.RouteNode || a.service !== serviceName) return;
4031
4123
  if (m && a.method !== "ALL" && a.method !== m) return;
4032
4124
  if (normalizePathTemplate(a.pathTemplate) === target) found = id;
4033
4125
  });
@@ -4058,7 +4150,7 @@ async function handleSpan(ctx, span) {
4058
4150
  let targetId;
4059
4151
  if (host) {
4060
4152
  ensureDatabaseNode(ctx.graph, host, span.dbSystem);
4061
- targetId = (0, import_types6.databaseId)(host);
4153
+ targetId = (0, import_types7.databaseId)(host);
4062
4154
  } else {
4063
4155
  const declared = findDeclaredDatabaseForService(ctx.graph, sourceId, span.dbSystem);
4064
4156
  if (declared) {
@@ -4075,7 +4167,7 @@ async function handleSpan(ctx, span) {
4075
4167
  }
4076
4168
  const result = upsertObservedEdge(
4077
4169
  ctx.graph,
4078
- import_types6.EdgeType.CONNECTS_TO,
4170
+ import_types7.EdgeType.CONNECTS_TO,
4079
4171
  observedSource(),
4080
4172
  targetId,
4081
4173
  ts,
@@ -4087,7 +4179,7 @@ async function handleSpan(ctx, span) {
4087
4179
  const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
4088
4180
  upsertObservedEdge(
4089
4181
  ctx.graph,
4090
- import_types6.EdgeType.CALLS,
4182
+ import_types7.EdgeType.CALLS,
4091
4183
  observedSource(),
4092
4184
  collectionId,
4093
4185
  ts,
@@ -4099,13 +4191,14 @@ async function handleSpan(ctx, span) {
4099
4191
  const tableId = ensureInfraNode(ctx.graph, "sql-table", span.dbTable, "self");
4100
4192
  upsertObservedEdge(
4101
4193
  ctx.graph,
4102
- import_types6.EdgeType.CALLS,
4194
+ import_types7.EdgeType.CALLS,
4103
4195
  observedSource(),
4104
4196
  tableId,
4105
4197
  ts,
4106
4198
  isError,
4107
4199
  callSiteEvidence
4108
4200
  );
4201
+ mergeObservedColumns(ctx.graph, tableId, span.dbColumns);
4109
4202
  }
4110
4203
  }
4111
4204
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
@@ -4114,7 +4207,7 @@ async function handleSpan(ctx, span) {
4114
4207
  span.messagingSystem,
4115
4208
  span.messagingDestination
4116
4209
  );
4117
- const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types6.EdgeType.CONSUMES_FROM : import_types6.EdgeType.PUBLISHES_TO;
4210
+ const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types7.EdgeType.CONSUMES_FROM : import_types7.EdgeType.PUBLISHES_TO;
4118
4211
  const result = upsertObservedEdge(
4119
4212
  ctx.graph,
4120
4213
  edgeType,
@@ -4134,7 +4227,7 @@ async function handleSpan(ctx, span) {
4134
4227
  );
4135
4228
  const result = upsertObservedEdge(
4136
4229
  ctx.graph,
4137
- import_types6.EdgeType.CONTAINS,
4230
+ import_types7.EdgeType.CONTAINS,
4138
4231
  observedSource(),
4139
4232
  targetId,
4140
4233
  ts,
@@ -4146,7 +4239,7 @@ async function handleSpan(ctx, span) {
4146
4239
  const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
4147
4240
  const result = upsertObservedEdge(
4148
4241
  ctx.graph,
4149
- import_types6.EdgeType.CONTAINS,
4242
+ import_types7.EdgeType.CONTAINS,
4150
4243
  observedSource(),
4151
4244
  targetId,
4152
4245
  ts,
@@ -4162,7 +4255,7 @@ async function handleSpan(ctx, span) {
4162
4255
  );
4163
4256
  const result = upsertObservedEdge(
4164
4257
  ctx.graph,
4165
- import_types6.EdgeType.CONNECTS_TO,
4258
+ import_types7.EdgeType.CONNECTS_TO,
4166
4259
  observedSource(),
4167
4260
  targetId,
4168
4261
  ts,
@@ -4178,7 +4271,7 @@ async function handleSpan(ctx, span) {
4178
4271
  if (targetId && targetId !== sourceId) {
4179
4272
  upsertObservedEdge(
4180
4273
  ctx.graph,
4181
- import_types6.EdgeType.CALLS,
4274
+ import_types7.EdgeType.CALLS,
4182
4275
  observedSource(),
4183
4276
  targetId,
4184
4277
  ts,
@@ -4191,7 +4284,7 @@ async function handleSpan(ctx, span) {
4191
4284
  const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
4192
4285
  upsertObservedEdge(
4193
4286
  ctx.graph,
4194
- import_types6.EdgeType.CALLS,
4287
+ import_types7.EdgeType.CALLS,
4195
4288
  observedSource(),
4196
4289
  frontierNodeId,
4197
4290
  ts,
@@ -4217,7 +4310,7 @@ async function handleSpan(ctx, span) {
4217
4310
  } : void 0;
4218
4311
  upsertObservedEdge(
4219
4312
  ctx.graph,
4220
- import_types6.EdgeType.CALLS,
4313
+ import_types7.EdgeType.CALLS,
4221
4314
  fallbackSource,
4222
4315
  sourceId,
4223
4316
  ts,
@@ -4227,7 +4320,7 @@ async function handleSpan(ctx, span) {
4227
4320
  }
4228
4321
  }
4229
4322
  }
4230
- if (span.httpRoute && (span.kind === 2 || span.kind === 0 || span.kind === void 0)) {
4323
+ if (span.httpRoute && (span.kind === 2 || span.kind === 1 || span.kind === 0 || span.kind === void 0)) {
4231
4324
  const routeNodeId = findRouteNodeByHttpRoute(
4232
4325
  ctx.graph,
4233
4326
  span.service,
@@ -4236,7 +4329,7 @@ async function handleSpan(ctx, span) {
4236
4329
  );
4237
4330
  if (routeNodeId) {
4238
4331
  const routeSvc = ctx.graph.getNodeAttributes(routeNodeId).service;
4239
- upsertObservedEdge(ctx.graph, import_types6.EdgeType.CONTAINS, (0, import_types6.serviceId)(routeSvc), routeNodeId, ts, isError);
4332
+ upsertObservedEdge(ctx.graph, import_types7.EdgeType.CONTAINS, (0, import_types7.serviceId)(routeSvc), routeNodeId, ts, isError);
4240
4333
  }
4241
4334
  }
4242
4335
  if (span.statusCode === 2) {
@@ -4275,7 +4368,7 @@ function promoteFrontierNodes(graph, opts = {}) {
4275
4368
  const aliasIndex = /* @__PURE__ */ new Map();
4276
4369
  graph.forEachNode((id, attrs) => {
4277
4370
  const a = attrs;
4278
- if (a.type !== import_types6.NodeType.ServiceNode) return;
4371
+ if (a.type !== import_types7.NodeType.ServiceNode) return;
4279
4372
  aliasIndex.set(a.name, id);
4280
4373
  if (a.aliases) {
4281
4374
  for (const alias of a.aliases) aliasIndex.set(alias, id);
@@ -4284,7 +4377,7 @@ function promoteFrontierNodes(graph, opts = {}) {
4284
4377
  const toPromote = [];
4285
4378
  graph.forEachNode((id, attrs) => {
4286
4379
  const a = attrs;
4287
- if (a.type !== import_types6.NodeType.FrontierNode) return;
4380
+ if (a.type !== import_types7.NodeType.FrontierNode) return;
4288
4381
  const target = aliasIndex.get(a.host);
4289
4382
  if (!target) return;
4290
4383
  if (target === id) return;
@@ -4318,7 +4411,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId7) {
4318
4411
  }
4319
4412
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
4320
4413
  graph.dropEdge(oldEdgeId);
4321
- const newId = edge.provenance === import_types6.Provenance.OBSERVED ? (0, import_types6.observedEdgeId)(newSource, newTarget, edge.type) : edge.provenance === import_types6.Provenance.INFERRED ? (0, import_types6.inferredEdgeId)(newSource, newTarget, edge.type) : (0, import_types6.extractedEdgeId)(newSource, newTarget, edge.type);
4414
+ const newId = edge.provenance === import_types7.Provenance.OBSERVED ? (0, import_types7.observedEdgeId)(newSource, newTarget, edge.type) : edge.provenance === import_types7.Provenance.INFERRED ? (0, import_types7.inferredEdgeId)(newSource, newTarget, edge.type) : (0, import_types7.extractedEdgeId)(newSource, newTarget, edge.type);
4322
4415
  if (graph.hasEdge(newId)) {
4323
4416
  const existing = graph.getEdgeAttributes(newId);
4324
4417
  const merged = {
@@ -4349,12 +4442,12 @@ async function markStaleEdges(graph, options = {}) {
4349
4442
  const project = options.project ?? DEFAULT_PROJECT;
4350
4443
  graph.forEachEdge((id, attrs) => {
4351
4444
  const e = attrs;
4352
- if (e.provenance !== import_types6.Provenance.OBSERVED) return;
4445
+ if (e.provenance !== import_types7.Provenance.OBSERVED) return;
4353
4446
  if (!e.lastObserved) return;
4354
4447
  const threshold = thresholdForEdgeType(e.type, thresholds);
4355
4448
  const age = now - new Date(e.lastObserved).getTime();
4356
4449
  if (age > threshold) {
4357
- const updated = { ...e, provenance: import_types6.Provenance.STALE, confidence: 0.3 };
4450
+ const updated = { ...e, provenance: import_types7.Provenance.STALE, confidence: 0.3 };
4358
4451
  graph.replaceEdgeAttributes(id, updated);
4359
4452
  events.push({
4360
4453
  edgeId: id,
@@ -4371,8 +4464,8 @@ async function markStaleEdges(graph, options = {}) {
4371
4464
  project,
4372
4465
  payload: {
4373
4466
  edgeId: id,
4374
- from: import_types6.Provenance.OBSERVED,
4375
- to: import_types6.Provenance.STALE
4467
+ from: import_types7.Provenance.OBSERVED,
4468
+ to: import_types7.Provenance.STALE
4376
4469
  }
4377
4470
  });
4378
4471
  }
@@ -4481,7 +4574,7 @@ function mergeSnapshot(graph, snapshot) {
4481
4574
  const validEdges = [];
4482
4575
  for (const node of incomingNodes) {
4483
4576
  if (node.attributes === void 0) continue;
4484
- const parsed = import_types6.GraphNodeSchema.safeParse(node.attributes);
4577
+ const parsed = import_types7.GraphNodeSchema.safeParse(node.attributes);
4485
4578
  if (!parsed.success) {
4486
4579
  issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
4487
4580
  continue;
@@ -4490,7 +4583,7 @@ function mergeSnapshot(graph, snapshot) {
4490
4583
  }
4491
4584
  for (const edge of incomingEdges) {
4492
4585
  if (edge.attributes === void 0) continue;
4493
- const parsed = import_types6.GraphEdgeSchema.safeParse(edge.attributes);
4586
+ const parsed = import_types7.GraphEdgeSchema.safeParse(edge.attributes);
4494
4587
  if (!parsed.success) {
4495
4588
  const label = edge.key ?? `${edge.source}->${edge.target}`;
4496
4589
  issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
@@ -4524,7 +4617,7 @@ var import_node_fs10 = require("fs");
4524
4617
  var import_node_path11 = __toESM(require("path"), 1);
4525
4618
  var import_ignore = __toESM(require("ignore"), 1);
4526
4619
  var import_minimatch2 = require("minimatch");
4527
- var import_types8 = require("@neat.is/types");
4620
+ var import_types9 = require("@neat.is/types");
4528
4621
 
4529
4622
  // src/extract/python.ts
4530
4623
  init_cjs_shims();
@@ -4597,7 +4690,7 @@ function pythonToPackage(service) {
4597
4690
  init_cjs_shims();
4598
4691
  var import_node_fs8 = require("fs");
4599
4692
  var import_node_path9 = __toESM(require("path"), 1);
4600
- var import_types7 = require("@neat.is/types");
4693
+ var import_types8 = require("@neat.is/types");
4601
4694
  function parseGoMod(source) {
4602
4695
  const module2 = source.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
4603
4696
  if (!module2) return null;
@@ -4625,8 +4718,8 @@ async function discoverGoService(scanPath, dir) {
4625
4718
  const name = mod.module.split("/").filter(Boolean).pop() ?? mod.module;
4626
4719
  const pkg = { name, dependencies: mod.dependencies };
4627
4720
  const node = {
4628
- id: (0, import_types7.serviceId)(name),
4629
- type: import_types7.NodeType.ServiceNode,
4721
+ id: (0, import_types8.serviceId)(name),
4722
+ type: import_types8.NodeType.ServiceNode,
4630
4723
  name,
4631
4724
  language: "go",
4632
4725
  dependencies: mod.dependencies,
@@ -4812,8 +4905,8 @@ async function discoverNodeService(scanPath, dir) {
4812
4905
  const framework = detectJsFramework(pkg);
4813
4906
  const language = await detectJsServiceLanguage(dir, pkg);
4814
4907
  const node = {
4815
- id: (0, import_types8.serviceId)(pkg.name),
4816
- type: import_types8.NodeType.ServiceNode,
4908
+ id: (0, import_types9.serviceId)(pkg.name),
4909
+ type: import_types9.NodeType.ServiceNode,
4817
4910
  name: pkg.name,
4818
4911
  language,
4819
4912
  version: pkg.version,
@@ -4829,8 +4922,8 @@ async function discoverPyService(scanPath, dir) {
4829
4922
  if (!py) return null;
4830
4923
  const pkg = pythonToPackage(py);
4831
4924
  const node = {
4832
- id: (0, import_types8.serviceId)(py.name),
4833
- type: import_types8.NodeType.ServiceNode,
4925
+ id: (0, import_types9.serviceId)(py.name),
4926
+ type: import_types9.NodeType.ServiceNode,
4834
4927
  name: py.name,
4835
4928
  language: "python",
4836
4929
  version: py.version,
@@ -4926,7 +5019,7 @@ init_cjs_shims();
4926
5019
  var import_node_path12 = __toESM(require("path"), 1);
4927
5020
  var import_node_fs11 = require("fs");
4928
5021
  var import_yaml2 = require("yaml");
4929
- var import_types9 = require("@neat.is/types");
5022
+ var import_types10 = require("@neat.is/types");
4930
5023
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
4931
5024
  "Service",
4932
5025
  "Deployment",
@@ -4936,7 +5029,7 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
4936
5029
  function addAliases(graph, serviceId7, candidates) {
4937
5030
  if (!graph.hasNode(serviceId7)) return;
4938
5031
  const node = graph.getNodeAttributes(serviceId7);
4939
- if (node.type !== import_types9.NodeType.ServiceNode) return;
5032
+ if (node.type !== import_types10.NodeType.ServiceNode) return;
4940
5033
  const set = new Set(node.aliases ?? []);
4941
5034
  for (const c of candidates) {
4942
5035
  if (!c) continue;
@@ -5117,7 +5210,7 @@ var import_node_path14 = __toESM(require("path"), 1);
5117
5210
  var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
5118
5211
  var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5119
5212
  var import_tree_sitter_typescript = __toESM(require("tree-sitter-typescript"), 1);
5120
- var import_types10 = require("@neat.is/types");
5213
+ var import_types11 = require("@neat.is/types");
5121
5214
  var PARSE_CHUNK2 = 16384;
5122
5215
  var GRAMMAR_BY_EXT = {
5123
5216
  ".ts": import_tree_sitter_typescript.default.typescript,
@@ -5208,7 +5301,7 @@ function disambiguate(defs) {
5208
5301
  }
5209
5302
  async function addSymbols(graph, services) {
5210
5303
  const parsers = /* @__PURE__ */ new Map();
5211
- const parserForExt = (ext) => {
5304
+ const parserForExt2 = (ext) => {
5212
5305
  const grammar = GRAMMAR_BY_EXT[ext];
5213
5306
  if (!grammar) return null;
5214
5307
  let parser = parsers.get(ext);
@@ -5224,7 +5317,7 @@ async function addSymbols(graph, services) {
5224
5317
  for (const service of services) {
5225
5318
  const files = await loadSourceFiles(service.dir);
5226
5319
  for (const file of files) {
5227
- const parser = parserForExt(import_node_path14.default.extname(file.path));
5320
+ const parser = parserForExt2(import_node_path14.default.extname(file.path));
5228
5321
  if (!parser) continue;
5229
5322
  const relPath = toPosix(import_node_path14.default.relative(service.dir, file.path));
5230
5323
  let defs;
@@ -5245,11 +5338,11 @@ async function addSymbols(graph, services) {
5245
5338
  nodesAdded += fn;
5246
5339
  edgesAdded += fe;
5247
5340
  for (const { def, disambiguator } of disambiguate(defs)) {
5248
- const sid = (0, import_types10.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
5341
+ const sid = (0, import_types11.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
5249
5342
  if (!graph.hasNode(sid)) {
5250
5343
  const node = {
5251
5344
  id: sid,
5252
- type: import_types10.NodeType.SymbolNode,
5345
+ type: import_types11.NodeType.SymbolNode,
5253
5346
  kind: def.kind,
5254
5347
  qualname: def.qualname,
5255
5348
  span: { startLine: def.startLine, endLine: def.endLine },
@@ -5260,15 +5353,15 @@ async function addSymbols(graph, services) {
5260
5353
  graph.addNode(sid, node);
5261
5354
  nodesAdded++;
5262
5355
  }
5263
- const containsId = (0, import_types10.extractedEdgeId)(fileNodeId, sid, import_types10.EdgeType.CONTAINS);
5356
+ const containsId = (0, import_types11.extractedEdgeId)(fileNodeId, sid, import_types11.EdgeType.CONTAINS);
5264
5357
  if (!graph.hasEdge(containsId)) {
5265
5358
  const edge = {
5266
5359
  id: containsId,
5267
5360
  source: fileNodeId,
5268
5361
  target: sid,
5269
- type: import_types10.EdgeType.CONTAINS,
5270
- provenance: import_types10.Provenance.EXTRACTED,
5271
- confidence: (0, import_types10.confidenceForExtracted)("structural"),
5362
+ type: import_types11.EdgeType.CONTAINS,
5363
+ provenance: import_types11.Provenance.EXTRACTED,
5364
+ confidence: (0, import_types11.confidenceForExtracted)("structural"),
5272
5365
  evidence: {
5273
5366
  file: relPath,
5274
5367
  line: def.startLine,
@@ -5288,7 +5381,7 @@ async function addSymbols(graph, services) {
5288
5381
  init_cjs_shims();
5289
5382
  var import_node_path16 = __toESM(require("path"), 1);
5290
5383
  var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
5291
- var import_types12 = require("@neat.is/types");
5384
+ var import_types13 = require("@neat.is/types");
5292
5385
 
5293
5386
  // src/extract/imports.ts
5294
5387
  init_cjs_shims();
@@ -5298,7 +5391,7 @@ var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5298
5391
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
5299
5392
  var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5300
5393
  var import_tree_sitter_go2 = __toESM(require("tree-sitter-go"), 1);
5301
- var import_types11 = require("@neat.is/types");
5394
+ var import_types12 = require("@neat.is/types");
5302
5395
  var PARSE_CHUNK3 = 16384;
5303
5396
  function parseSource3(parser, source) {
5304
5397
  return parser.parse(
@@ -5572,17 +5665,17 @@ async function resolveGoImport(specifier, modulePath, serviceDir) {
5572
5665
  return toPosix(import_node_path15.default.relative(serviceDir, candidates[0]));
5573
5666
  }
5574
5667
  function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
5575
- const importeeFileId = (0, import_types11.fileId)(serviceName, importeeRelPath);
5668
+ const importeeFileId = (0, import_types12.fileId)(serviceName, importeeRelPath);
5576
5669
  if (!graph.hasNode(importeeFileId)) return 0;
5577
- const edgeId = (0, import_types11.extractedEdgeId)(importerFileId, importeeFileId, import_types11.EdgeType.IMPORTS);
5670
+ const edgeId = (0, import_types12.extractedEdgeId)(importerFileId, importeeFileId, import_types12.EdgeType.IMPORTS);
5578
5671
  if (graph.hasEdge(edgeId)) return 0;
5579
5672
  const edge = {
5580
5673
  id: edgeId,
5581
5674
  source: importerFileId,
5582
5675
  target: importeeFileId,
5583
- type: import_types11.EdgeType.IMPORTS,
5584
- provenance: import_types11.Provenance.EXTRACTED,
5585
- confidence: (0, import_types11.confidenceForExtracted)("structural"),
5676
+ type: import_types12.EdgeType.IMPORTS,
5677
+ provenance: import_types12.Provenance.EXTRACTED,
5678
+ confidence: (0, import_types12.confidenceForExtracted)("structural"),
5586
5679
  evidence: { file: importerRelPath, line, snippet: snippet2 }
5587
5680
  };
5588
5681
  graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
@@ -5599,7 +5692,7 @@ async function addImports(graph, services) {
5599
5692
  for (const file of files) {
5600
5693
  if (isTestPath(file.path)) continue;
5601
5694
  const relFile = toPosix(import_node_path15.default.relative(service.dir, file.path));
5602
- const importerFileId = (0, import_types11.fileId)(service.pkg.name, relFile);
5695
+ const importerFileId = (0, import_types12.fileId)(service.pkg.name, relFile);
5603
5696
  const isPython = import_node_path15.default.extname(file.path) === ".py";
5604
5697
  const isGo = import_node_path15.default.extname(file.path) === ".go";
5605
5698
  if (isGo) {
@@ -5760,7 +5853,7 @@ function stringInner(node) {
5760
5853
  }
5761
5854
  async function addSymbolEdges(graph, services) {
5762
5855
  const parsers = /* @__PURE__ */ new Map();
5763
- const parserForExt = (ext) => {
5856
+ const parserForExt2 = (ext) => {
5764
5857
  const grammar = GRAMMAR_BY_EXT[ext];
5765
5858
  if (!grammar) return null;
5766
5859
  let parser = parsers.get(ext);
@@ -5777,7 +5870,7 @@ async function addSymbolEdges(graph, services) {
5777
5870
  const tsPaths = await loadTsPathConfig(service.dir);
5778
5871
  const files = await loadSourceFiles(service.dir);
5779
5872
  for (const file of files) {
5780
- const parser = parserForExt(import_node_path16.default.extname(file.path));
5873
+ const parser = parserForExt2(import_node_path16.default.extname(file.path));
5781
5874
  if (!parser) continue;
5782
5875
  const relPath = toPosix(import_node_path16.default.relative(service.dir, file.path));
5783
5876
  const fileDir = import_node_path16.default.dirname(file.path);
@@ -5790,7 +5883,7 @@ async function addSymbolEdges(graph, services) {
5790
5883
  }
5791
5884
  const disambiguated = disambiguate(collectSymbolDefs(root));
5792
5885
  const locals = disambiguated.map(({ def, disambiguator }) => ({
5793
- sid: (0, import_types12.symbolId)(serviceName, relPath, def.qualname, disambiguator),
5886
+ sid: (0, import_types13.symbolId)(serviceName, relPath, def.qualname, disambiguator),
5794
5887
  qualname: def.qualname,
5795
5888
  kind: def.kind,
5796
5889
  startLine: def.startLine,
@@ -5819,10 +5912,10 @@ async function addSymbolEdges(graph, services) {
5819
5912
  if (local) return local.kind === wantKind ? local.sid : null;
5820
5913
  const imp = resolvedImports.get(name);
5821
5914
  if (imp) {
5822
- const candidate = (0, import_types12.symbolId)(serviceName, imp.targetRelPath, imp.importedName);
5915
+ const candidate = (0, import_types13.symbolId)(serviceName, imp.targetRelPath, imp.importedName);
5823
5916
  if (graph.hasNode(candidate)) {
5824
5917
  const node = graph.getNodeAttributes(candidate);
5825
- if (node.type === import_types12.NodeType.SymbolNode && node.kind === wantKind) return candidate;
5918
+ if (node.type === import_types13.NodeType.SymbolNode && node.kind === wantKind) return candidate;
5826
5919
  }
5827
5920
  }
5828
5921
  return null;
@@ -5849,7 +5942,7 @@ async function addSymbolEdges(graph, services) {
5849
5942
  sourceSid: self.sid,
5850
5943
  targetName: ext.name,
5851
5944
  wantKind: "class",
5852
- edgeType: import_types12.EdgeType.INHERITS,
5945
+ edgeType: import_types13.EdgeType.INHERITS,
5853
5946
  line: ext.line
5854
5947
  });
5855
5948
  }
@@ -5858,7 +5951,7 @@ async function addSymbolEdges(graph, services) {
5858
5951
  sourceSid: self.sid,
5859
5952
  targetName: impl.name,
5860
5953
  wantKind: "class",
5861
- edgeType: import_types12.EdgeType.IMPLEMENTS,
5954
+ edgeType: import_types13.EdgeType.IMPLEMENTS,
5862
5955
  line: impl.line
5863
5956
  });
5864
5957
  }
@@ -5874,7 +5967,7 @@ async function addSymbolEdges(graph, services) {
5874
5967
  sourceSid: caller.sid,
5875
5968
  targetName: fn.text,
5876
5969
  wantKind: "function",
5877
- edgeType: import_types12.EdgeType.CALLS,
5970
+ edgeType: import_types13.EdgeType.CALLS,
5878
5971
  line
5879
5972
  });
5880
5973
  }
@@ -5890,15 +5983,15 @@ async function addSymbolEdges(graph, services) {
5890
5983
  const targetSid = resolveTarget(req2.targetName, req2.wantKind);
5891
5984
  if (!targetSid) continue;
5892
5985
  if (targetSid === req2.sourceSid) continue;
5893
- const edgeId = (0, import_types12.extractedEdgeId)(req2.sourceSid, targetSid, req2.edgeType);
5986
+ const edgeId = (0, import_types13.extractedEdgeId)(req2.sourceSid, targetSid, req2.edgeType);
5894
5987
  if (graph.hasEdge(edgeId)) continue;
5895
5988
  const edge = {
5896
5989
  id: edgeId,
5897
5990
  source: req2.sourceSid,
5898
5991
  target: targetSid,
5899
5992
  type: req2.edgeType,
5900
- provenance: import_types12.Provenance.EXTRACTED,
5901
- confidence: (0, import_types12.confidenceForExtracted)("structural"),
5993
+ provenance: import_types13.Provenance.EXTRACTED,
5994
+ confidence: (0, import_types13.confidenceForExtracted)("structural"),
5902
5995
  evidence: {
5903
5996
  file: relPath,
5904
5997
  line: req2.line,
@@ -5916,7 +6009,7 @@ async function addSymbolEdges(graph, services) {
5916
6009
  // src/extract/databases/index.ts
5917
6010
  init_cjs_shims();
5918
6011
  var import_node_path24 = __toESM(require("path"), 1);
5919
- var import_types13 = require("@neat.is/types");
6012
+ var import_types14 = require("@neat.is/types");
5920
6013
 
5921
6014
  // src/extract/databases/db-config-yaml.ts
5922
6015
  init_cjs_shims();
@@ -6420,8 +6513,8 @@ function compatibleDriversFor(engine) {
6420
6513
  }
6421
6514
  function toDatabaseNode(config) {
6422
6515
  return {
6423
- id: (0, import_types13.databaseId)(config.host),
6424
- type: import_types13.NodeType.DatabaseNode,
6516
+ id: (0, import_types14.databaseId)(config.host),
6517
+ type: import_types14.NodeType.DatabaseNode,
6425
6518
  name: config.database || config.host,
6426
6519
  engine: config.engine,
6427
6520
  engineVersion: config.engineVersion,
@@ -6562,12 +6655,12 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
6562
6655
  edgesAdded += fe;
6563
6656
  const evidenceFile = toPosix(import_node_path24.default.relative(scanPath, config.sourceFile));
6564
6657
  const edge = {
6565
- id: (0, import_types3.extractedEdgeId)(fileNodeId, dbNode.id, import_types13.EdgeType.CONNECTS_TO),
6658
+ id: (0, import_types3.extractedEdgeId)(fileNodeId, dbNode.id, import_types14.EdgeType.CONNECTS_TO),
6566
6659
  source: fileNodeId,
6567
6660
  target: dbNode.id,
6568
- type: import_types13.EdgeType.CONNECTS_TO,
6569
- provenance: import_types13.Provenance.EXTRACTED,
6570
- confidence: (0, import_types13.confidenceForExtracted)("structural"),
6661
+ type: import_types14.EdgeType.CONNECTS_TO,
6662
+ provenance: import_types14.Provenance.EXTRACTED,
6663
+ confidence: (0, import_types14.confidenceForExtracted)("structural"),
6571
6664
  evidence: { file: evidenceFile }
6572
6665
  };
6573
6666
  if (!graph.hasEdge(edge.id)) {
@@ -6579,11 +6672,11 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
6579
6672
  const primary = allConfigs[0];
6580
6673
  service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
6581
6674
  const relPath = import_node_path24.default.relative(scanPath, primary.sourceFile);
6582
- const cfgId = (0, import_types13.configId)(relPath);
6675
+ const cfgId = (0, import_types14.configId)(relPath);
6583
6676
  if (!graph.hasNode(cfgId)) {
6584
6677
  const cfgNode = {
6585
6678
  id: cfgId,
6586
- type: import_types13.NodeType.ConfigNode,
6679
+ type: import_types14.NodeType.ConfigNode,
6587
6680
  name: import_node_path24.default.basename(primary.sourceFile),
6588
6681
  path: relPath,
6589
6682
  fileType: isConfigFile(import_node_path24.default.basename(primary.sourceFile)).fileType || "config"
@@ -6592,12 +6685,12 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
6592
6685
  nodesAdded++;
6593
6686
  }
6594
6687
  const cfgEdge = {
6595
- id: (0, import_types3.extractedEdgeId)(service.node.id, cfgId, import_types13.EdgeType.CONFIGURED_BY),
6688
+ id: (0, import_types3.extractedEdgeId)(service.node.id, cfgId, import_types14.EdgeType.CONFIGURED_BY),
6596
6689
  source: service.node.id,
6597
6690
  target: cfgId,
6598
- type: import_types13.EdgeType.CONFIGURED_BY,
6599
- provenance: import_types13.Provenance.EXTRACTED,
6600
- confidence: (0, import_types13.confidenceForExtracted)("structural"),
6691
+ type: import_types14.EdgeType.CONFIGURED_BY,
6692
+ provenance: import_types14.Provenance.EXTRACTED,
6693
+ confidence: (0, import_types14.confidenceForExtracted)("structural"),
6601
6694
  evidence: { file: toPosix(relPath) }
6602
6695
  };
6603
6696
  if (!graph.hasEdge(cfgEdge.id)) {
@@ -6629,7 +6722,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
6629
6722
  init_cjs_shims();
6630
6723
  var import_node_fs15 = require("fs");
6631
6724
  var import_node_path25 = __toESM(require("path"), 1);
6632
- var import_types14 = require("@neat.is/types");
6725
+ var import_types15 = require("@neat.is/types");
6633
6726
  async function walkConfigFiles(dir) {
6634
6727
  const out = [];
6635
6728
  async function walk6(current) {
@@ -6656,8 +6749,8 @@ async function addConfigNodes(graph, services, scanPath) {
6656
6749
  for (const file of configFiles) {
6657
6750
  const relPath = import_node_path25.default.relative(scanPath, file);
6658
6751
  const node = {
6659
- id: (0, import_types14.configId)(relPath),
6660
- type: import_types14.NodeType.ConfigNode,
6752
+ id: (0, import_types15.configId)(relPath),
6753
+ type: import_types15.NodeType.ConfigNode,
6661
6754
  name: import_node_path25.default.basename(file),
6662
6755
  path: relPath,
6663
6756
  fileType: isConfigFile(import_node_path25.default.basename(file)).fileType
@@ -6676,12 +6769,12 @@ async function addConfigNodes(graph, services, scanPath) {
6676
6769
  nodesAdded += fn;
6677
6770
  edgesAdded += fe;
6678
6771
  const edge = {
6679
- id: (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types14.EdgeType.CONFIGURED_BY),
6772
+ id: (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types15.EdgeType.CONFIGURED_BY),
6680
6773
  source: fileNodeId,
6681
6774
  target: node.id,
6682
- type: import_types14.EdgeType.CONFIGURED_BY,
6683
- provenance: import_types14.Provenance.EXTRACTED,
6684
- confidence: (0, import_types14.confidenceForExtracted)("structural"),
6775
+ type: import_types15.EdgeType.CONFIGURED_BY,
6776
+ provenance: import_types15.Provenance.EXTRACTED,
6777
+ confidence: (0, import_types15.confidenceForExtracted)("structural"),
6685
6778
  evidence: { file: relPath.split(import_node_path25.default.sep).join("/") }
6686
6779
  };
6687
6780
  if (!graph.hasEdge(edge.id)) {
@@ -6697,7 +6790,7 @@ async function addConfigNodes(graph, services, scanPath) {
6697
6790
  init_cjs_shims();
6698
6791
  var import_node_fs16 = require("fs");
6699
6792
  var import_node_path26 = __toESM(require("path"), 1);
6700
- var import_types15 = require("@neat.is/types");
6793
+ var import_types16 = require("@neat.is/types");
6701
6794
  var PROTO_EXTENSION = ".proto";
6702
6795
  function packageOf(content) {
6703
6796
  const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
@@ -6775,11 +6868,11 @@ async function addGrpcMethods(graph, services) {
6775
6868
  }
6776
6869
  if (methods.length === 0) continue;
6777
6870
  for (const method of methods) {
6778
- const mid = (0, import_types15.grpcMethodId)(method.rpcService, method.rpcMethod);
6871
+ const mid = (0, import_types16.grpcMethodId)(method.rpcService, method.rpcMethod);
6779
6872
  if (!graph.hasNode(mid)) {
6780
6873
  const node = {
6781
6874
  id: mid,
6782
- type: import_types15.NodeType.GrpcMethodNode,
6875
+ type: import_types16.NodeType.GrpcMethodNode,
6783
6876
  name: `${method.rpcService}/${method.rpcMethod}`,
6784
6877
  rpcService: method.rpcService,
6785
6878
  rpcMethod: method.rpcMethod,
@@ -6790,15 +6883,15 @@ async function addGrpcMethods(graph, services) {
6790
6883
  graph.addNode(mid, node);
6791
6884
  nodesAdded++;
6792
6885
  }
6793
- const containsId = (0, import_types15.extractedEdgeId)(service.node.id, mid, import_types15.EdgeType.CONTAINS);
6886
+ const containsId = (0, import_types16.extractedEdgeId)(service.node.id, mid, import_types16.EdgeType.CONTAINS);
6794
6887
  if (!graph.hasEdge(containsId)) {
6795
6888
  const edge = {
6796
6889
  id: containsId,
6797
6890
  source: service.node.id,
6798
6891
  target: mid,
6799
- type: import_types15.EdgeType.CONTAINS,
6800
- provenance: import_types15.Provenance.EXTRACTED,
6801
- confidence: (0, import_types15.confidenceForExtracted)("structural"),
6892
+ type: import_types16.EdgeType.CONTAINS,
6893
+ provenance: import_types16.Provenance.EXTRACTED,
6894
+ confidence: (0, import_types16.confidenceForExtracted)("structural"),
6802
6895
  evidence: {
6803
6896
  file: relFile,
6804
6897
  line: method.line,
@@ -6816,7 +6909,7 @@ async function addGrpcMethods(graph, services) {
6816
6909
 
6817
6910
  // src/extract/calls/index.ts
6818
6911
  init_cjs_shims();
6819
- var import_types27 = require("@neat.is/types");
6912
+ var import_types29 = require("@neat.is/types");
6820
6913
 
6821
6914
  // src/extract/calls/http.ts
6822
6915
  init_cjs_shims();
@@ -6824,7 +6917,7 @@ var import_node_path27 = __toESM(require("path"), 1);
6824
6917
  var import_tree_sitter5 = __toESM(require("tree-sitter"), 1);
6825
6918
  var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
6826
6919
  var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
6827
- var import_types16 = require("@neat.is/types");
6920
+ var import_types17 = require("@neat.is/types");
6828
6921
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
6829
6922
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
6830
6923
  function isInsideJsxExternalLink(node) {
@@ -6910,7 +7003,7 @@ async function addHttpCallEdges(graph, services) {
6910
7003
  const dedupKey = `${relFile}|${targetId}`;
6911
7004
  if (seen.has(dedupKey)) continue;
6912
7005
  seen.add(dedupKey);
6913
- const confidence = (0, import_types16.confidenceForExtracted)("url-literal-service-target");
7006
+ const confidence = (0, import_types17.confidenceForExtracted)("url-literal-service-target");
6914
7007
  const ev = {
6915
7008
  file: relFile,
6916
7009
  line: site.line,
@@ -6924,25 +7017,25 @@ async function addHttpCallEdges(graph, services) {
6924
7017
  );
6925
7018
  nodesAdded += n;
6926
7019
  edgesAdded += e;
6927
- if (!(0, import_types16.passesExtractedFloor)(confidence)) {
7020
+ if (!(0, import_types17.passesExtractedFloor)(confidence)) {
6928
7021
  noteExtractedDropped({
6929
7022
  source: fileNodeId,
6930
7023
  target: targetId,
6931
- type: import_types16.EdgeType.CALLS,
7024
+ type: import_types17.EdgeType.CALLS,
6932
7025
  confidence,
6933
7026
  confidenceKind: "url-literal-service-target",
6934
7027
  evidence: ev
6935
7028
  });
6936
7029
  continue;
6937
7030
  }
6938
- const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, targetId, import_types16.EdgeType.CALLS);
7031
+ const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, targetId, import_types17.EdgeType.CALLS);
6939
7032
  if (!graph.hasEdge(edgeId)) {
6940
7033
  const edge = {
6941
7034
  id: edgeId,
6942
7035
  source: fileNodeId,
6943
7036
  target: targetId,
6944
- type: import_types16.EdgeType.CALLS,
6945
- provenance: import_types16.Provenance.EXTRACTED,
7037
+ type: import_types17.EdgeType.CALLS,
7038
+ provenance: import_types17.Provenance.EXTRACTED,
6946
7039
  confidence,
6947
7040
  evidence: ev
6948
7041
  };
@@ -6960,7 +7053,7 @@ init_cjs_shims();
6960
7053
  var import_node_path28 = __toESM(require("path"), 1);
6961
7054
  var import_tree_sitter6 = __toESM(require("tree-sitter"), 1);
6962
7055
  var import_tree_sitter_javascript5 = __toESM(require("tree-sitter-javascript"), 1);
6963
- var import_types17 = require("@neat.is/types");
7056
+ var import_types18 = require("@neat.is/types");
6964
7057
  var PARSE_CHUNK5 = 16384;
6965
7058
  function parseSource5(parser, source) {
6966
7059
  return parser.parse(
@@ -7125,9 +7218,9 @@ function buildRouteIndex(graph) {
7125
7218
  const index = /* @__PURE__ */ new Map();
7126
7219
  graph.forEachNode((_id, attrs) => {
7127
7220
  const node = attrs;
7128
- if (node.type !== import_types17.NodeType.RouteNode) return;
7221
+ if (node.type !== import_types18.NodeType.RouteNode) return;
7129
7222
  const route = attrs;
7130
- const owner = (0, import_types17.serviceId)(route.service);
7223
+ const owner = (0, import_types18.serviceId)(route.service);
7131
7224
  const entry2 = {
7132
7225
  method: route.method.toUpperCase(),
7133
7226
  normalizedPath: normalizePathTemplate(route.pathTemplate),
@@ -7185,7 +7278,7 @@ async function addRouteCallEdges(graph, services) {
7185
7278
  );
7186
7279
  nodesAdded += n;
7187
7280
  edgesAdded += e;
7188
- const confidence = (0, import_types17.confidenceForExtracted)("verified-call-site");
7281
+ const confidence = (0, import_types18.confidenceForExtracted)("verified-call-site");
7189
7282
  const ev = {
7190
7283
  file: relFile,
7191
7284
  line: site.line,
@@ -7193,25 +7286,25 @@ async function addRouteCallEdges(graph, services) {
7193
7286
  method: site.method ?? match.method,
7194
7287
  pathTemplate: site.pathTemplate
7195
7288
  };
7196
- if (!(0, import_types17.passesExtractedFloor)(confidence)) {
7289
+ if (!(0, import_types18.passesExtractedFloor)(confidence)) {
7197
7290
  noteExtractedDropped({
7198
7291
  source: fileNodeId,
7199
7292
  target: match.routeNodeId,
7200
- type: import_types17.EdgeType.CALLS,
7293
+ type: import_types18.EdgeType.CALLS,
7201
7294
  confidence,
7202
7295
  confidenceKind: "verified-call-site",
7203
7296
  evidence: ev
7204
7297
  });
7205
7298
  continue;
7206
7299
  }
7207
- const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types17.EdgeType.CALLS);
7300
+ const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types18.EdgeType.CALLS);
7208
7301
  if (!graph.hasEdge(edgeId)) {
7209
7302
  const edge = {
7210
7303
  id: edgeId,
7211
7304
  source: fileNodeId,
7212
7305
  target: match.routeNodeId,
7213
- type: import_types17.EdgeType.CALLS,
7214
- provenance: import_types17.Provenance.EXTRACTED,
7306
+ type: import_types18.EdgeType.CALLS,
7307
+ provenance: import_types18.Provenance.EXTRACTED,
7215
7308
  confidence,
7216
7309
  evidence: ev
7217
7310
  };
@@ -7227,7 +7320,7 @@ async function addRouteCallEdges(graph, services) {
7227
7320
  // src/extract/calls/kafka.ts
7228
7321
  init_cjs_shims();
7229
7322
  var import_node_path29 = __toESM(require("path"), 1);
7230
- var import_types18 = require("@neat.is/types");
7323
+ var import_types19 = require("@neat.is/types");
7231
7324
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
7232
7325
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
7233
7326
  function findAll(re, text) {
@@ -7248,7 +7341,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
7248
7341
  seen.add(key);
7249
7342
  const line = lineOf(file.content, topic);
7250
7343
  out.push({
7251
- infraId: (0, import_types18.infraId)("kafka-topic", topic),
7344
+ infraId: (0, import_types19.infraId)("kafka-topic", topic),
7252
7345
  name: topic,
7253
7346
  kind: "kafka-topic",
7254
7347
  edgeType,
@@ -7271,7 +7364,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
7271
7364
  // src/extract/calls/redis.ts
7272
7365
  init_cjs_shims();
7273
7366
  var import_node_path30 = __toESM(require("path"), 1);
7274
- var import_types19 = require("@neat.is/types");
7367
+ var import_types20 = require("@neat.is/types");
7275
7368
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
7276
7369
  function redisEndpointsFromFile(file, serviceDir) {
7277
7370
  const out = [];
@@ -7284,7 +7377,7 @@ function redisEndpointsFromFile(file, serviceDir) {
7284
7377
  seen.add(host);
7285
7378
  const line = lineOf(file.content, host);
7286
7379
  out.push({
7287
- infraId: (0, import_types19.infraId)("redis", host),
7380
+ infraId: (0, import_types20.infraId)("redis", host),
7288
7381
  name: host,
7289
7382
  kind: "redis",
7290
7383
  edgeType: "CALLS",
@@ -7305,7 +7398,7 @@ function redisEndpointsFromFile(file, serviceDir) {
7305
7398
  // src/extract/calls/aws.ts
7306
7399
  init_cjs_shims();
7307
7400
  var import_node_path31 = __toESM(require("path"), 1);
7308
- var import_types20 = require("@neat.is/types");
7401
+ var import_types21 = require("@neat.is/types");
7309
7402
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
7310
7403
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
7311
7404
  function hasMarker(text, markers) {
@@ -7329,7 +7422,7 @@ function awsEndpointsFromFile(file, serviceDir) {
7329
7422
  seen.add(key);
7330
7423
  const line = lineOf(file.content, name);
7331
7424
  out.push({
7332
- infraId: (0, import_types20.infraId)(kind, name),
7425
+ infraId: (0, import_types21.infraId)(kind, name),
7333
7426
  name,
7334
7427
  kind,
7335
7428
  edgeType: "CALLS",
@@ -7364,7 +7457,7 @@ function awsEndpointsFromFile(file, serviceDir) {
7364
7457
  // src/extract/calls/grpc.ts
7365
7458
  init_cjs_shims();
7366
7459
  var import_node_path32 = __toESM(require("path"), 1);
7367
- var import_types21 = require("@neat.is/types");
7460
+ var import_types22 = require("@neat.is/types");
7368
7461
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
7369
7462
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
7370
7463
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -7413,7 +7506,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
7413
7506
  const { kind } = classified;
7414
7507
  const line = lineOf(file.content, m[0]);
7415
7508
  out.push({
7416
- infraId: (0, import_types21.infraId)(kind, name),
7509
+ infraId: (0, import_types22.infraId)(kind, name),
7417
7510
  name,
7418
7511
  kind,
7419
7512
  edgeType: "CALLS",
@@ -7434,7 +7527,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
7434
7527
  // src/extract/calls/supabase.ts
7435
7528
  init_cjs_shims();
7436
7529
  var import_node_path33 = __toESM(require("path"), 1);
7437
- var import_types22 = require("@neat.is/types");
7530
+ var import_types23 = require("@neat.is/types");
7438
7531
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
7439
7532
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
7440
7533
  var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
@@ -7482,7 +7575,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
7482
7575
  seen.add(name);
7483
7576
  const line = lineOf(file.content, m[0]);
7484
7577
  out.push({
7485
- infraId: (0, import_types22.infraId)("supabase", name),
7578
+ infraId: (0, import_types23.infraId)("supabase", name),
7486
7579
  name,
7487
7580
  kind: "supabase",
7488
7581
  edgeType: "CALLS",
@@ -7513,7 +7606,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
7513
7606
  seen.add(key);
7514
7607
  const line = lineOf(file.content, am[0]);
7515
7608
  out.push({
7516
- infraId: (0, import_types22.infraId)(kind, resource),
7609
+ infraId: (0, import_types23.infraId)(kind, resource),
7517
7610
  name: resource,
7518
7611
  kind,
7519
7612
  edgeType: "CALLS",
@@ -7532,7 +7625,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
7532
7625
  // src/extract/calls/mongoose.ts
7533
7626
  init_cjs_shims();
7534
7627
  var import_node_path34 = __toESM(require("path"), 1);
7535
- var import_types23 = require("@neat.is/types");
7628
+ var import_types24 = require("@neat.is/types");
7536
7629
  var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
7537
7630
  var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
7538
7631
  var PLURALIZE_DISABLED_RE = /\bpluralize\s*\(\s*(?:null|false)\s*\)/;
@@ -7677,7 +7770,7 @@ function collectModelDefs(content, pluralizeOn) {
7677
7770
  function endpoint(r, file, serviceDir, matchText) {
7678
7771
  const line = lineOf(file.content, matchText);
7679
7772
  return {
7680
- infraId: (0, import_types23.infraId)(r.kind, r.name),
7773
+ infraId: (0, import_types24.infraId)(r.kind, r.name),
7681
7774
  name: r.name,
7682
7775
  kind: r.kind,
7683
7776
  edgeType: "CALLS",
@@ -7830,7 +7923,7 @@ init_cjs_shims();
7830
7923
  var import_node_path35 = __toESM(require("path"), 1);
7831
7924
  var import_tree_sitter7 = __toESM(require("tree-sitter"), 1);
7832
7925
  var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
7833
- var import_types24 = require("@neat.is/types");
7926
+ var import_types25 = require("@neat.is/types");
7834
7927
  var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
7835
7928
  var PARSE_CHUNK6 = 16384;
7836
7929
  function makePyParser4() {
@@ -7890,20 +7983,57 @@ function walk3(node, visit) {
7890
7983
  visit(node);
7891
7984
  for (const c of namedChildren(node)) walk3(c, visit);
7892
7985
  }
7986
+ var COLUMN_BUILDERS = /* @__PURE__ */ new Set(["Column", "mapped_column"]);
7987
+ function isColumnBuilder(call) {
7988
+ const fn = call.childForFieldName("function");
7989
+ const t = fn?.text;
7990
+ if (!t) return false;
7991
+ const base = t.includes(".") ? t.slice(t.lastIndexOf(".") + 1) : t;
7992
+ return COLUMN_BUILDERS.has(base);
7993
+ }
7994
+ function firstPositionalString(call) {
7995
+ const args = call.childForFieldName("arguments");
7996
+ if (!args) return null;
7997
+ for (const arg of namedChildren(args)) {
7998
+ if (arg.type === "keyword_argument") continue;
7999
+ return arg.type === "string" ? pyStaticStringText2(arg) : null;
8000
+ }
8001
+ return null;
8002
+ }
8003
+ function columnsFromClassBody(body) {
8004
+ const out = [];
8005
+ const seen = /* @__PURE__ */ new Set();
8006
+ for (const stmt of namedChildren(body)) {
8007
+ if (stmt.type !== "expression_statement") continue;
8008
+ const assign = stmt.namedChild(0);
8009
+ if (assign?.type !== "assignment") continue;
8010
+ const left = assign.childForFieldName("left");
8011
+ if (left?.type !== "identifier") continue;
8012
+ const right = assign.childForFieldName("right");
8013
+ if (right?.type !== "call" || !isColumnBuilder(right)) continue;
8014
+ const name = firstPositionalString(right) ?? left.text;
8015
+ if (name && !seen.has(name)) {
8016
+ seen.add(name);
8017
+ out.push(name);
8018
+ }
8019
+ }
8020
+ return out;
8021
+ }
7893
8022
  function sqlalchemyEndpointsFromFile(file, serviceDir) {
7894
8023
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
7895
8024
  const tree = parseSource6(makePyParser4(), file.content);
7896
8025
  const out = [];
7897
8026
  const seen = /* @__PURE__ */ new Set();
7898
- const push = (name, line) => {
8027
+ const push = (name, line, columns) => {
7899
8028
  if (seen.has(name)) return;
7900
8029
  seen.add(name);
7901
8030
  out.push({
7902
- infraId: (0, import_types24.infraId)("sql-table", name),
8031
+ infraId: (0, import_types25.infraId)("sql-table", name),
7903
8032
  name,
7904
8033
  kind: "sql-table",
7905
8034
  edgeType: "CALLS",
7906
8035
  confidenceKind: "verified-call-site",
8036
+ ...columns && columns.length > 0 ? { columns } : {},
7907
8037
  evidence: {
7908
8038
  file: import_node_path35.default.relative(serviceDir, file.path),
7909
8039
  line,
@@ -7919,11 +8049,12 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
7919
8049
  const line = node.startPosition.row + 1;
7920
8050
  const explicit = explicitTablename(body);
7921
8051
  if (explicit === "computed") return;
8052
+ const columns = columnsFromClassBody(body);
7922
8053
  if (explicit) {
7923
- push(explicit.name, line);
8054
+ push(explicit.name, line, columns);
7924
8055
  return;
7925
8056
  }
7926
- if (extendsFlaskModel(node)) push(flaskSqlalchemyTableName(nameNode.text), line);
8057
+ if (extendsFlaskModel(node)) push(flaskSqlalchemyTableName(nameNode.text), line, columns);
7927
8058
  return;
7928
8059
  }
7929
8060
  if (node.type === "call") {
@@ -8005,7 +8136,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
8005
8136
  if (seen.has(key)) continue;
8006
8137
  seen.add(key);
8007
8138
  out.push({
8008
- infraId: (0, import_types24.infraId)("sql-table", t),
8139
+ infraId: (0, import_types25.infraId)("sql-table", t),
8009
8140
  name: t,
8010
8141
  kind: "sql-table",
8011
8142
  edgeType: "CALLS",
@@ -8026,7 +8157,7 @@ init_cjs_shims();
8026
8157
  var import_node_path36 = __toESM(require("path"), 1);
8027
8158
  var import_tree_sitter8 = __toESM(require("tree-sitter"), 1);
8028
8159
  var import_tree_sitter_python5 = __toESM(require("tree-sitter-python"), 1);
8029
- var import_types25 = require("@neat.is/types");
8160
+ var import_types26 = require("@neat.is/types");
8030
8161
  var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
8031
8162
  var PARSE_CHUNK7 = 16384;
8032
8163
  function makePyParser5() {
@@ -8106,7 +8237,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
8106
8237
  seen.add(table);
8107
8238
  const line = node.startPosition.row + 1;
8108
8239
  out.push({
8109
- infraId: (0, import_types25.infraId)("sql-table", table),
8240
+ infraId: (0, import_types26.infraId)("sql-table", table),
8110
8241
  name: table,
8111
8242
  kind: "sql-table",
8112
8243
  edgeType: "CALLS",
@@ -8117,12 +8248,129 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
8117
8248
  return out;
8118
8249
  }
8119
8250
 
8120
- // src/extract/calls/go.ts
8251
+ // src/extract/calls/drizzle.ts
8121
8252
  init_cjs_shims();
8122
- var import_node_path39 = __toESM(require("path"), 1);
8253
+ var import_node_path37 = __toESM(require("path"), 1);
8123
8254
  var import_tree_sitter9 = __toESM(require("tree-sitter"), 1);
8255
+ var import_tree_sitter_javascript6 = __toESM(require("tree-sitter-javascript"), 1);
8256
+ var import_types27 = require("@neat.is/types");
8257
+ var DRIZZLE_IMPORT_RE = /drizzle-orm/;
8258
+ var TABLE_BUILDERS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
8259
+ function parserForExt(ext) {
8260
+ const p = new import_tree_sitter9.default();
8261
+ p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript6.default);
8262
+ return p;
8263
+ }
8264
+ function namedChildren3(node) {
8265
+ const out = [];
8266
+ for (let i = 0; i < node.namedChildCount; i++) {
8267
+ const c = node.namedChild(i);
8268
+ if (c) out.push(c);
8269
+ }
8270
+ return out;
8271
+ }
8272
+ function stringLiteralText2(node) {
8273
+ if (!node || node.type !== "string") return null;
8274
+ for (const child of namedChildren3(node)) {
8275
+ if (child.type === "string_fragment") return child.text;
8276
+ }
8277
+ return "";
8278
+ }
8279
+ function firstStringArg(call) {
8280
+ const args = call.childForFieldName("arguments");
8281
+ if (!args) return null;
8282
+ for (const arg of namedChildren3(args)) {
8283
+ if (arg.type === "string") return stringLiteralText2(arg);
8284
+ return null;
8285
+ }
8286
+ return null;
8287
+ }
8288
+ function builderColumnName(value) {
8289
+ let node = value;
8290
+ while (node) {
8291
+ if (node.type === "call_expression") {
8292
+ const fn = node.childForFieldName("function");
8293
+ if (fn?.type === "identifier") return firstStringArg(node);
8294
+ if (fn?.type === "member_expression") {
8295
+ node = fn.childForFieldName("object");
8296
+ continue;
8297
+ }
8298
+ return null;
8299
+ }
8300
+ if (node.type === "member_expression") {
8301
+ node = node.childForFieldName("object");
8302
+ continue;
8303
+ }
8304
+ return null;
8305
+ }
8306
+ return null;
8307
+ }
8308
+ function keyName(key) {
8309
+ if (!key) return null;
8310
+ if (key.type === "property_identifier") return key.text;
8311
+ if (key.type === "string") return stringLiteralText2(key);
8312
+ return null;
8313
+ }
8314
+ function columnsFromObject(obj) {
8315
+ const out = [];
8316
+ const seen = /* @__PURE__ */ new Set();
8317
+ for (const child of namedChildren3(obj)) {
8318
+ if (child.type !== "pair") continue;
8319
+ const value = child.childForFieldName("value");
8320
+ const key = keyName(child.childForFieldName("key"));
8321
+ const name = (value ? builderColumnName(value) : null) ?? key;
8322
+ if (name && !seen.has(name)) {
8323
+ seen.add(name);
8324
+ out.push(name);
8325
+ }
8326
+ }
8327
+ return out;
8328
+ }
8329
+ function drizzleEndpointsFromFile(file, serviceDir) {
8330
+ if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
8331
+ const tree = parseSource2(parserForExt(import_node_path37.default.extname(file.path)), file.content);
8332
+ const out = [];
8333
+ const seen = /* @__PURE__ */ new Set();
8334
+ const walk6 = (node) => {
8335
+ if (node.type === "call_expression") {
8336
+ const fn = node.childForFieldName("function");
8337
+ if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
8338
+ const args = node.childForFieldName("arguments");
8339
+ const argNodes = args ? namedChildren3(args) : [];
8340
+ const tableName = stringLiteralText2(argNodes[0] ?? null);
8341
+ const obj = argNodes[1];
8342
+ if (tableName && obj?.type === "object" && !seen.has(tableName)) {
8343
+ seen.add(tableName);
8344
+ const columns = columnsFromObject(obj);
8345
+ const line = node.startPosition.row + 1;
8346
+ out.push({
8347
+ infraId: (0, import_types27.infraId)("sql-table", tableName),
8348
+ name: tableName,
8349
+ kind: "sql-table",
8350
+ edgeType: "CALLS",
8351
+ confidenceKind: "structural",
8352
+ columns,
8353
+ evidence: {
8354
+ file: import_node_path37.default.relative(serviceDir, file.path),
8355
+ line,
8356
+ snippet: snippet(file.content, line)
8357
+ }
8358
+ });
8359
+ }
8360
+ }
8361
+ }
8362
+ for (const c of namedChildren3(node)) walk6(c);
8363
+ };
8364
+ walk6(tree.rootNode);
8365
+ return out;
8366
+ }
8367
+
8368
+ // src/extract/calls/go.ts
8369
+ init_cjs_shims();
8370
+ var import_node_path40 = __toESM(require("path"), 1);
8371
+ var import_tree_sitter10 = __toESM(require("tree-sitter"), 1);
8124
8372
  var import_tree_sitter_go3 = __toESM(require("tree-sitter-go"), 1);
8125
- var import_types26 = require("@neat.is/types");
8373
+ var import_types28 = require("@neat.is/types");
8126
8374
  init_otel();
8127
8375
  var SQL_METHODS = /* @__PURE__ */ new Set(["Exec", "ExecContext", "Query", "QueryContext", "QueryRow", "QueryRowContext"]);
8128
8376
  var PARSE_CHUNK8 = 16384;
@@ -8134,8 +8382,8 @@ function walk5(node, visit) {
8134
8382
  }
8135
8383
  }
8136
8384
  function goSqlEndpointsFromFile(file, serviceDir) {
8137
- if (import_node_path39.default.extname(file.path) !== ".go") return [];
8138
- const parser = new import_tree_sitter9.default();
8385
+ if (import_node_path40.default.extname(file.path) !== ".go") return [];
8386
+ const parser = new import_tree_sitter10.default();
8139
8387
  parser.setLanguage(import_tree_sitter_go3.default);
8140
8388
  const tree = parser.parse(
8141
8389
  (index) => index >= file.content.length ? "" : file.content.slice(index, index + PARSE_CHUNK8)
@@ -8154,12 +8402,12 @@ function goSqlEndpointsFromFile(file, serviceDir) {
8154
8402
  if (!table) return;
8155
8403
  const line = node.startPosition.row + 1;
8156
8404
  out.push({
8157
- infraId: (0, import_types26.infraId)("sql-table", table),
8405
+ infraId: (0, import_types28.infraId)("sql-table", table),
8158
8406
  name: table,
8159
8407
  kind: "sql-table",
8160
8408
  edgeType: "CALLS",
8161
8409
  confidenceKind: "verified-call-site",
8162
- evidence: { file: toPosix(import_node_path39.default.relative(serviceDir, file.path)), line, snippet: snippet(file.content, line) }
8410
+ evidence: { file: toPosix(import_node_path40.default.relative(serviceDir, file.path)), line, snippet: snippet(file.content, line) }
8163
8411
  });
8164
8412
  });
8165
8413
  return out;
@@ -8169,11 +8417,11 @@ function goSqlEndpointsFromFile(file, serviceDir) {
8169
8417
  function edgeTypeFromEndpoint(ep) {
8170
8418
  switch (ep.edgeType) {
8171
8419
  case "PUBLISHES_TO":
8172
- return import_types27.EdgeType.PUBLISHES_TO;
8420
+ return import_types29.EdgeType.PUBLISHES_TO;
8173
8421
  case "CONSUMES_FROM":
8174
- return import_types27.EdgeType.CONSUMES_FROM;
8422
+ return import_types29.EdgeType.CONSUMES_FROM;
8175
8423
  default:
8176
- return import_types27.EdgeType.CALLS;
8424
+ return import_types29.EdgeType.CALLS;
8177
8425
  }
8178
8426
  }
8179
8427
  function isAwsKind(kind) {
@@ -8199,6 +8447,7 @@ async function addExternalEndpointEdges(graph, services) {
8199
8447
  endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
8200
8448
  endpoints.push(...sqlalchemyEndpointsFromFile(maskedFile, service.dir));
8201
8449
  endpoints.push(...djangoOrmEndpointsFromFile(maskedFile, service.dir));
8450
+ endpoints.push(...drizzleEndpointsFromFile(maskedFile, service.dir));
8202
8451
  try {
8203
8452
  endpoints.push(...goSqlEndpointsFromFile(maskedFile, service.dir));
8204
8453
  } catch (err) {
@@ -8213,7 +8462,7 @@ async function addExternalEndpointEdges(graph, services) {
8213
8462
  if (!graph.hasNode(ep.infraId)) {
8214
8463
  const node = {
8215
8464
  id: ep.infraId,
8216
- type: import_types27.NodeType.InfraNode,
8465
+ type: import_types29.NodeType.InfraNode,
8217
8466
  name: ep.name,
8218
8467
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
8219
8468
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -8224,8 +8473,22 @@ async function addExternalEndpointEdges(graph, services) {
8224
8473
  graph.addNode(node.id, node);
8225
8474
  nodesAdded++;
8226
8475
  }
8476
+ if (ep.columns && ep.columns.length > 0) {
8477
+ const node = graph.getNodeAttributes(ep.infraId);
8478
+ if (node.type === import_types29.NodeType.InfraNode) {
8479
+ graph.replaceNodeAttributes(ep.infraId, {
8480
+ ...node,
8481
+ columns: foldColumns(
8482
+ node.columns,
8483
+ ep.columns,
8484
+ import_types29.Provenance.EXTRACTED,
8485
+ (0, import_types29.confidenceForExtracted)(ep.confidenceKind)
8486
+ )
8487
+ });
8488
+ }
8489
+ }
8227
8490
  const edgeType = edgeTypeFromEndpoint(ep);
8228
- const confidence = (0, import_types27.confidenceForExtracted)(ep.confidenceKind);
8491
+ const confidence = (0, import_types29.confidenceForExtracted)(ep.confidenceKind);
8229
8492
  const relFile = toPosix(ep.evidence.file);
8230
8493
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
8231
8494
  graph,
@@ -8235,7 +8498,7 @@ async function addExternalEndpointEdges(graph, services) {
8235
8498
  );
8236
8499
  nodesAdded += n;
8237
8500
  edgesAdded += e;
8238
- if (!(0, import_types27.passesExtractedFloor)(confidence)) {
8501
+ if (!(0, import_types29.passesExtractedFloor)(confidence)) {
8239
8502
  noteExtractedDropped({
8240
8503
  source: fileNodeId,
8241
8504
  target: ep.infraId,
@@ -8255,7 +8518,7 @@ async function addExternalEndpointEdges(graph, services) {
8255
8518
  source: fileNodeId,
8256
8519
  target: ep.infraId,
8257
8520
  type: edgeType,
8258
- provenance: import_types27.Provenance.EXTRACTED,
8521
+ provenance: import_types29.Provenance.EXTRACTED,
8259
8522
  confidence,
8260
8523
  evidence: ep.evidence
8261
8524
  };
@@ -8281,16 +8544,16 @@ init_cjs_shims();
8281
8544
 
8282
8545
  // src/extract/infra/docker-compose.ts
8283
8546
  init_cjs_shims();
8284
- var import_node_path40 = __toESM(require("path"), 1);
8285
- var import_types29 = require("@neat.is/types");
8547
+ var import_node_path41 = __toESM(require("path"), 1);
8548
+ var import_types31 = require("@neat.is/types");
8286
8549
 
8287
8550
  // src/extract/infra/shared.ts
8288
8551
  init_cjs_shims();
8289
- var import_types28 = require("@neat.is/types");
8552
+ var import_types30 = require("@neat.is/types");
8290
8553
  function makeInfraNode(kind, name, provider = "self", extras) {
8291
8554
  return {
8292
- id: (0, import_types28.infraId)(kind, name),
8293
- type: import_types28.NodeType.InfraNode,
8555
+ id: (0, import_types30.infraId)(kind, name),
8556
+ type: import_types30.NodeType.InfraNode,
8294
8557
  name,
8295
8558
  provider,
8296
8559
  kind,
@@ -8334,8 +8597,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
8334
8597
  source: anchorId,
8335
8598
  target: node.id,
8336
8599
  type: edgeType,
8337
- provenance: import_types28.Provenance.EXTRACTED,
8338
- confidence: (0, import_types28.confidenceForExtracted)("structural"),
8600
+ provenance: import_types30.Provenance.EXTRACTED,
8601
+ confidence: (0, import_types30.confidenceForExtracted)("structural"),
8339
8602
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
8340
8603
  };
8341
8604
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -8352,7 +8615,7 @@ function dependsOnList(value) {
8352
8615
  }
8353
8616
  function serviceNameToServiceNode(name, services) {
8354
8617
  for (const s of services) {
8355
- if (s.node.name === name || import_node_path40.default.basename(s.dir) === name) return s.node.id;
8618
+ if (s.node.name === name || import_node_path41.default.basename(s.dir) === name) return s.node.id;
8356
8619
  }
8357
8620
  return null;
8358
8621
  }
@@ -8361,7 +8624,7 @@ async function addComposeInfra(graph, scanPath, services) {
8361
8624
  let edgesAdded = 0;
8362
8625
  let composePath = null;
8363
8626
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
8364
- const abs = import_node_path40.default.join(scanPath, name);
8627
+ const abs = import_node_path41.default.join(scanPath, name);
8365
8628
  if (await exists(abs)) {
8366
8629
  composePath = abs;
8367
8630
  break;
@@ -8374,13 +8637,13 @@ async function addComposeInfra(graph, scanPath, services) {
8374
8637
  } catch (err) {
8375
8638
  recordExtractionError(
8376
8639
  "infra docker-compose",
8377
- import_node_path40.default.relative(scanPath, composePath),
8640
+ import_node_path41.default.relative(scanPath, composePath),
8378
8641
  err
8379
8642
  );
8380
8643
  return { nodesAdded, edgesAdded };
8381
8644
  }
8382
8645
  if (!compose?.services) return { nodesAdded, edgesAdded };
8383
- const evidenceFile = import_node_path40.default.relative(scanPath, composePath).split(import_node_path40.default.sep).join("/");
8646
+ const evidenceFile = import_node_path41.default.relative(scanPath, composePath).split(import_node_path41.default.sep).join("/");
8384
8647
  const composeNameToNodeId = /* @__PURE__ */ new Map();
8385
8648
  for (const [composeName, svc] of Object.entries(compose.services)) {
8386
8649
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -8402,15 +8665,15 @@ async function addComposeInfra(graph, scanPath, services) {
8402
8665
  for (const dep of dependsOnList(svc.depends_on)) {
8403
8666
  const targetId = composeNameToNodeId.get(dep);
8404
8667
  if (!targetId) continue;
8405
- const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types29.EdgeType.DEPENDS_ON);
8668
+ const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types31.EdgeType.DEPENDS_ON);
8406
8669
  if (graph.hasEdge(edgeId)) continue;
8407
8670
  const edge = {
8408
8671
  id: edgeId,
8409
8672
  source: sourceId,
8410
8673
  target: targetId,
8411
- type: import_types29.EdgeType.DEPENDS_ON,
8412
- provenance: import_types29.Provenance.EXTRACTED,
8413
- confidence: (0, import_types29.confidenceForExtracted)("structural"),
8674
+ type: import_types31.EdgeType.DEPENDS_ON,
8675
+ provenance: import_types31.Provenance.EXTRACTED,
8676
+ confidence: (0, import_types31.confidenceForExtracted)("structural"),
8414
8677
  evidence: { file: evidenceFile }
8415
8678
  };
8416
8679
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -8422,9 +8685,9 @@ async function addComposeInfra(graph, scanPath, services) {
8422
8685
 
8423
8686
  // src/extract/infra/dockerfile.ts
8424
8687
  init_cjs_shims();
8425
- var import_node_path41 = __toESM(require("path"), 1);
8688
+ var import_node_path42 = __toESM(require("path"), 1);
8426
8689
  var import_node_fs17 = require("fs");
8427
- var import_types30 = require("@neat.is/types");
8690
+ var import_types32 = require("@neat.is/types");
8428
8691
  function readDockerfile(content) {
8429
8692
  let image = null;
8430
8693
  const ports = [];
@@ -8453,7 +8716,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
8453
8716
  let nodesAdded = 0;
8454
8717
  let edgesAdded = 0;
8455
8718
  for (const service of services) {
8456
- const dockerfilePath = import_node_path41.default.join(service.dir, "Dockerfile");
8719
+ const dockerfilePath = import_node_path42.default.join(service.dir, "Dockerfile");
8457
8720
  if (!await exists(dockerfilePath)) continue;
8458
8721
  let content;
8459
8722
  try {
@@ -8461,7 +8724,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
8461
8724
  } catch (err) {
8462
8725
  recordExtractionError(
8463
8726
  "infra dockerfile",
8464
- import_node_path41.default.relative(scanPath, dockerfilePath),
8727
+ import_node_path42.default.relative(scanPath, dockerfilePath),
8465
8728
  err
8466
8729
  );
8467
8730
  continue;
@@ -8473,8 +8736,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
8473
8736
  graph.addNode(node.id, node);
8474
8737
  nodesAdded++;
8475
8738
  }
8476
- const relDockerfile = toPosix(import_node_path41.default.relative(service.dir, dockerfilePath));
8477
- const evidenceFile = toPosix(import_node_path41.default.relative(scanPath, dockerfilePath));
8739
+ const relDockerfile = toPosix(import_node_path42.default.relative(service.dir, dockerfilePath));
8740
+ const evidenceFile = toPosix(import_node_path42.default.relative(scanPath, dockerfilePath));
8478
8741
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
8479
8742
  graph,
8480
8743
  service.pkg.name,
@@ -8483,15 +8746,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
8483
8746
  );
8484
8747
  nodesAdded += fn;
8485
8748
  edgesAdded += fe;
8486
- const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types30.EdgeType.RUNS_ON);
8749
+ const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types32.EdgeType.RUNS_ON);
8487
8750
  if (!graph.hasEdge(edgeId)) {
8488
8751
  const edge = {
8489
8752
  id: edgeId,
8490
8753
  source: fileNodeId,
8491
8754
  target: node.id,
8492
- type: import_types30.EdgeType.RUNS_ON,
8493
- provenance: import_types30.Provenance.EXTRACTED,
8494
- confidence: (0, import_types30.confidenceForExtracted)("structural"),
8755
+ type: import_types32.EdgeType.RUNS_ON,
8756
+ provenance: import_types32.Provenance.EXTRACTED,
8757
+ confidence: (0, import_types32.confidenceForExtracted)("structural"),
8495
8758
  evidence: {
8496
8759
  file: evidenceFile,
8497
8760
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -8506,15 +8769,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
8506
8769
  graph.addNode(portNode.id, portNode);
8507
8770
  nodesAdded++;
8508
8771
  }
8509
- const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types30.EdgeType.CONNECTS_TO);
8772
+ const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types32.EdgeType.CONNECTS_TO);
8510
8773
  if (graph.hasEdge(portEdgeId)) continue;
8511
8774
  const portEdge = {
8512
8775
  id: portEdgeId,
8513
8776
  source: fileNodeId,
8514
8777
  target: portNode.id,
8515
- type: import_types30.EdgeType.CONNECTS_TO,
8516
- provenance: import_types30.Provenance.EXTRACTED,
8517
- confidence: (0, import_types30.confidenceForExtracted)("structural"),
8778
+ type: import_types32.EdgeType.CONNECTS_TO,
8779
+ provenance: import_types32.Provenance.EXTRACTED,
8780
+ confidence: (0, import_types32.confidenceForExtracted)("structural"),
8518
8781
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
8519
8782
  };
8520
8783
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -8527,8 +8790,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
8527
8790
  // src/extract/infra/terraform.ts
8528
8791
  init_cjs_shims();
8529
8792
  var import_node_fs18 = require("fs");
8530
- var import_node_path42 = __toESM(require("path"), 1);
8531
- var import_types31 = require("@neat.is/types");
8793
+ var import_node_path43 = __toESM(require("path"), 1);
8794
+ var import_types33 = require("@neat.is/types");
8532
8795
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
8533
8796
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
8534
8797
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -8538,11 +8801,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
8538
8801
  for (const entry2 of entries) {
8539
8802
  if (entry2.isDirectory()) {
8540
8803
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
8541
- const child = import_node_path42.default.join(start, entry2.name);
8804
+ const child = import_node_path43.default.join(start, entry2.name);
8542
8805
  if (await isPythonVenvDir(child)) continue;
8543
8806
  out.push(...await walkTfFiles(child, depth + 1, max));
8544
8807
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
8545
- out.push(import_node_path42.default.join(start, entry2.name));
8808
+ out.push(import_node_path43.default.join(start, entry2.name));
8546
8809
  }
8547
8810
  }
8548
8811
  return out;
@@ -8574,7 +8837,7 @@ async function addTerraformResources(graph, scanPath) {
8574
8837
  const files = await walkTfFiles(scanPath);
8575
8838
  for (const file of files) {
8576
8839
  const content = await import_node_fs18.promises.readFile(file, "utf8");
8577
- const evidenceFile = toPosix(import_node_path42.default.relative(scanPath, file));
8840
+ const evidenceFile = toPosix(import_node_path43.default.relative(scanPath, file));
8578
8841
  const resources = [];
8579
8842
  const byKey = /* @__PURE__ */ new Map();
8580
8843
  RESOURCE_RE.lastIndex = 0;
@@ -8609,16 +8872,16 @@ async function addTerraformResources(graph, scanPath) {
8609
8872
  if (!target) continue;
8610
8873
  if (seen.has(target.nodeId)) continue;
8611
8874
  seen.add(target.nodeId);
8612
- const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types31.EdgeType.DEPENDS_ON);
8875
+ const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types33.EdgeType.DEPENDS_ON);
8613
8876
  if (graph.hasEdge(edgeId)) continue;
8614
8877
  const line = lineAt2(content, resource.bodyOffset + ref.index);
8615
8878
  const edge = {
8616
8879
  id: edgeId,
8617
8880
  source: resource.nodeId,
8618
8881
  target: target.nodeId,
8619
- type: import_types31.EdgeType.DEPENDS_ON,
8620
- provenance: import_types31.Provenance.EXTRACTED,
8621
- confidence: (0, import_types31.confidenceForExtracted)("structural"),
8882
+ type: import_types33.EdgeType.DEPENDS_ON,
8883
+ provenance: import_types33.Provenance.EXTRACTED,
8884
+ confidence: (0, import_types33.confidenceForExtracted)("structural"),
8622
8885
  evidence: { file: evidenceFile, line, snippet: key }
8623
8886
  };
8624
8887
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -8632,7 +8895,7 @@ async function addTerraformResources(graph, scanPath) {
8632
8895
  // src/extract/infra/k8s.ts
8633
8896
  init_cjs_shims();
8634
8897
  var import_node_fs19 = require("fs");
8635
- var import_node_path43 = __toESM(require("path"), 1);
8898
+ var import_node_path44 = __toESM(require("path"), 1);
8636
8899
  var import_yaml3 = require("yaml");
8637
8900
  var K8S_KIND_TO_INFRA_KIND = {
8638
8901
  Service: "k8s-service",
@@ -8650,11 +8913,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
8650
8913
  for (const entry2 of entries) {
8651
8914
  if (entry2.isDirectory()) {
8652
8915
  if (IGNORED_DIRS.has(entry2.name)) continue;
8653
- const child = import_node_path43.default.join(start, entry2.name);
8916
+ const child = import_node_path44.default.join(start, entry2.name);
8654
8917
  if (await isPythonVenvDir(child)) continue;
8655
8918
  out.push(...await walkYamlFiles2(child, depth + 1, max));
8656
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path43.default.extname(entry2.name))) {
8657
- out.push(import_node_path43.default.join(start, entry2.name));
8919
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path44.default.extname(entry2.name))) {
8920
+ out.push(import_node_path44.default.join(start, entry2.name));
8658
8921
  }
8659
8922
  }
8660
8923
  return out;
@@ -8688,13 +8951,13 @@ async function addK8sResources(graph, scanPath) {
8688
8951
  // src/extract/infra/cloudflare.ts
8689
8952
  init_cjs_shims();
8690
8953
  var import_node_fs20 = require("fs");
8691
- var import_node_path44 = __toESM(require("path"), 1);
8954
+ var import_node_path45 = __toESM(require("path"), 1);
8692
8955
  var import_smol_toml2 = require("smol-toml");
8693
- var import_types32 = require("@neat.is/types");
8956
+ var import_types34 = require("@neat.is/types");
8694
8957
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
8695
8958
  async function readWranglerConfig(dir) {
8696
8959
  for (const filename of WRANGLER_FILENAMES) {
8697
- const abs = import_node_path44.default.join(dir, filename);
8960
+ const abs = import_node_path45.default.join(dir, filename);
8698
8961
  if (!await exists(abs)) continue;
8699
8962
  const raw = await import_node_fs20.promises.readFile(abs, "utf8");
8700
8963
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -8738,8 +9001,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
8738
9001
  source: anchorId,
8739
9002
  target: node.id,
8740
9003
  type: edgeType,
8741
- provenance: import_types32.Provenance.EXTRACTED,
8742
- confidence: (0, import_types32.confidenceForExtracted)("structural"),
9004
+ provenance: import_types34.Provenance.EXTRACTED,
9005
+ confidence: (0, import_types34.confidenceForExtracted)("structural"),
8743
9006
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
8744
9007
  };
8745
9008
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -8757,11 +9020,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8757
9020
  try {
8758
9021
  read = await readWranglerConfig(service.dir);
8759
9022
  } catch (err) {
8760
- recordExtractionError("infra cloudflare", import_node_path44.default.relative(scanPath, service.dir), err);
9023
+ recordExtractionError("infra cloudflare", import_node_path45.default.relative(scanPath, service.dir), err);
8761
9024
  continue;
8762
9025
  }
8763
9026
  if (!read || !read.config.name) continue;
8764
- const evidenceFile = toPosix(import_node_path44.default.relative(scanPath, import_node_path44.default.join(service.dir, read.relFile)));
9027
+ const evidenceFile = toPosix(import_node_path45.default.relative(scanPath, import_node_path45.default.join(service.dir, read.relFile)));
8765
9028
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
8766
9029
  }
8767
9030
  for (const worker of discovered) {
@@ -8773,7 +9036,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8773
9036
  }
8774
9037
  let anchorId = service.node.id;
8775
9038
  if (config.main) {
8776
- const entryRelPath = toPosix(import_node_path44.default.normalize(config.main));
9039
+ const entryRelPath = toPosix(import_node_path45.default.normalize(config.main));
8777
9040
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
8778
9041
  graph,
8779
9042
  service.pkg.name,
@@ -8800,15 +9063,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8800
9063
  nodesAdded++;
8801
9064
  }
8802
9065
  if (runtimeNode.id !== anchorId) {
8803
- const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types32.EdgeType.RUNS_ON);
9066
+ const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types34.EdgeType.RUNS_ON);
8804
9067
  if (!graph.hasEdge(runsOnId)) {
8805
9068
  const edge = {
8806
9069
  id: runsOnId,
8807
9070
  source: anchorId,
8808
9071
  target: runtimeNode.id,
8809
- type: import_types32.EdgeType.RUNS_ON,
8810
- provenance: import_types32.Provenance.EXTRACTED,
8811
- confidence: (0, import_types32.confidenceForExtracted)("structural"),
9072
+ type: import_types34.EdgeType.RUNS_ON,
9073
+ provenance: import_types34.Provenance.EXTRACTED,
9074
+ confidence: (0, import_types34.confidenceForExtracted)("structural"),
8812
9075
  evidence: {
8813
9076
  file: evidenceFile,
8814
9077
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -8822,7 +9085,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8822
9085
  const result = addResourceEdge(
8823
9086
  graph,
8824
9087
  anchorId,
8825
- import_types32.EdgeType.CONNECTS_TO,
9088
+ import_types34.EdgeType.CONNECTS_TO,
8826
9089
  "cloudflare-route",
8827
9090
  route,
8828
9091
  evidenceFile,
@@ -8846,7 +9109,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8846
9109
  const result = addResourceEdge(
8847
9110
  graph,
8848
9111
  anchorId,
8849
- import_types32.EdgeType.DEPENDS_ON,
9112
+ import_types34.EdgeType.DEPENDS_ON,
8850
9113
  group.kind,
8851
9114
  name,
8852
9115
  evidenceFile,
@@ -8860,7 +9123,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8860
9123
  const result = addResourceEdge(
8861
9124
  graph,
8862
9125
  anchorId,
8863
- import_types32.EdgeType.DEPENDS_ON,
9126
+ import_types34.EdgeType.DEPENDS_ON,
8864
9127
  "cloudflare-cron",
8865
9128
  cron,
8866
9129
  evidenceFile,
@@ -8873,7 +9136,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8873
9136
  const result = addResourceEdge(
8874
9137
  graph,
8875
9138
  anchorId,
8876
- import_types32.EdgeType.DEPENDS_ON,
9139
+ import_types34.EdgeType.DEPENDS_ON,
8877
9140
  "cloudflare-env-var",
8878
9141
  varName,
8879
9142
  evidenceFile,
@@ -8886,15 +9149,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8886
9149
  if (!svc.service) continue;
8887
9150
  const target = workerIndex.get(svc.service);
8888
9151
  if (target && target.anchorId !== anchorId) {
8889
- const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types32.EdgeType.CALLS);
9152
+ const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types34.EdgeType.CALLS);
8890
9153
  if (!graph.hasEdge(edgeId)) {
8891
9154
  const edge = {
8892
9155
  id: edgeId,
8893
9156
  source: anchorId,
8894
9157
  target: target.anchorId,
8895
- type: import_types32.EdgeType.CALLS,
8896
- provenance: import_types32.Provenance.EXTRACTED,
8897
- confidence: (0, import_types32.confidenceForExtracted)("structural"),
9158
+ type: import_types34.EdgeType.CALLS,
9159
+ provenance: import_types34.Provenance.EXTRACTED,
9160
+ confidence: (0, import_types34.confidenceForExtracted)("structural"),
8898
9161
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
8899
9162
  };
8900
9163
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -8905,7 +9168,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8905
9168
  const result = addResourceEdge(
8906
9169
  graph,
8907
9170
  anchorId,
8908
- import_types32.EdgeType.DEPENDS_ON,
9171
+ import_types34.EdgeType.DEPENDS_ON,
8909
9172
  "cloudflare-service-binding",
8910
9173
  svc.service,
8911
9174
  evidenceFile,
@@ -8921,12 +9184,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8921
9184
  // src/extract/infra/vercel.ts
8922
9185
  init_cjs_shims();
8923
9186
  var import_node_fs21 = require("fs");
8924
- var import_node_path45 = __toESM(require("path"), 1);
8925
- var import_types33 = require("@neat.is/types");
9187
+ var import_node_path46 = __toESM(require("path"), 1);
9188
+ var import_types35 = require("@neat.is/types");
8926
9189
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
8927
9190
  async function readVercelConfig(dir) {
8928
9191
  for (const filename of VERCEL_CONFIG_FILENAMES) {
8929
- const abs = import_node_path45.default.join(dir, filename);
9192
+ const abs = import_node_path46.default.join(dir, filename);
8930
9193
  if (!await exists(abs)) continue;
8931
9194
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
8932
9195
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -8935,7 +9198,7 @@ async function readVercelConfig(dir) {
8935
9198
  return null;
8936
9199
  }
8937
9200
  async function readLinkedProjectName(dir) {
8938
- const abs = import_node_path45.default.join(dir, ".vercel", "project.json");
9201
+ const abs = import_node_path46.default.join(dir, ".vercel", "project.json");
8939
9202
  if (!await exists(abs)) return void 0;
8940
9203
  const parsed = JSON.parse(await import_node_fs21.promises.readFile(abs, "utf8"));
8941
9204
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -8953,7 +9216,7 @@ async function addVercelServices(graph, services, scanPath) {
8953
9216
  read = await readVercelConfig(service.dir);
8954
9217
  projectName = await readLinkedProjectName(service.dir);
8955
9218
  } catch (err) {
8956
- recordExtractionError("infra vercel", import_node_path45.default.relative(scanPath, service.dir), err);
9219
+ recordExtractionError("infra vercel", import_node_path46.default.relative(scanPath, service.dir), err);
8957
9220
  continue;
8958
9221
  }
8959
9222
  if (!read && !projectName) continue;
@@ -8969,7 +9232,7 @@ async function addVercelServices(graph, services, scanPath) {
8969
9232
  const anchorId = service.node.id;
8970
9233
  if (!read) continue;
8971
9234
  const { config, relFile, raw } = read;
8972
- const evidenceFile = toPosix(import_node_path45.default.relative(scanPath, import_node_path45.default.join(service.dir, relFile)));
9235
+ const evidenceFile = toPosix(import_node_path46.default.relative(scanPath, import_node_path46.default.join(service.dir, relFile)));
8973
9236
  const add = (edgeType, kind, name) => {
8974
9237
  if (!name) return;
8975
9238
  const result = emitPlatformResourceEdge(
@@ -8985,12 +9248,12 @@ async function addVercelServices(graph, services, scanPath) {
8985
9248
  nodesAdded += result.nodesAdded;
8986
9249
  edgesAdded += result.edgesAdded;
8987
9250
  };
8988
- add(import_types33.EdgeType.RUNS_ON, "vercel", "vercel");
8989
- for (const cron of config.crons ?? []) add(import_types33.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
8990
- for (const varName of Object.keys(config.env ?? {})) add(import_types33.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
8991
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types33.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
9251
+ add(import_types35.EdgeType.RUNS_ON, "vercel", "vercel");
9252
+ for (const cron of config.crons ?? []) add(import_types35.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
9253
+ for (const varName of Object.keys(config.env ?? {})) add(import_types35.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
9254
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types35.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
8992
9255
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
8993
- add(import_types33.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
9256
+ add(import_types35.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
8994
9257
  }
8995
9258
  }
8996
9259
  return { nodesAdded, edgesAdded };
@@ -8999,13 +9262,13 @@ async function addVercelServices(graph, services, scanPath) {
8999
9262
  // src/extract/infra/railway.ts
9000
9263
  init_cjs_shims();
9001
9264
  var import_node_fs22 = require("fs");
9002
- var import_node_path46 = __toESM(require("path"), 1);
9265
+ var import_node_path47 = __toESM(require("path"), 1);
9003
9266
  var import_smol_toml3 = require("smol-toml");
9004
- var import_types34 = require("@neat.is/types");
9267
+ var import_types36 = require("@neat.is/types");
9005
9268
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
9006
9269
  async function readRailwayConfig(dir) {
9007
9270
  for (const filename of RAILWAY_FILENAMES) {
9008
- const abs = import_node_path46.default.join(dir, filename);
9271
+ const abs = import_node_path47.default.join(dir, filename);
9009
9272
  if (!await exists(abs)) continue;
9010
9273
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
9011
9274
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -9021,7 +9284,7 @@ async function addRailwayServices(graph, services, scanPath) {
9021
9284
  try {
9022
9285
  read = await readRailwayConfig(service.dir);
9023
9286
  } catch (err) {
9024
- recordExtractionError("infra railway", import_node_path46.default.relative(scanPath, service.dir), err);
9287
+ recordExtractionError("infra railway", import_node_path47.default.relative(scanPath, service.dir), err);
9025
9288
  continue;
9026
9289
  }
9027
9290
  if (!read) continue;
@@ -9031,7 +9294,7 @@ async function addRailwayServices(graph, services, scanPath) {
9031
9294
  }
9032
9295
  const anchorId = service.node.id;
9033
9296
  const { config, relFile, raw } = read;
9034
- const evidenceFile = toPosix(import_node_path46.default.relative(scanPath, import_node_path46.default.join(service.dir, relFile)));
9297
+ const evidenceFile = toPosix(import_node_path47.default.relative(scanPath, import_node_path47.default.join(service.dir, relFile)));
9035
9298
  const add = (edgeType, kind, name) => {
9036
9299
  if (!name) return;
9037
9300
  const result = emitPlatformResourceEdge(
@@ -9047,9 +9310,9 @@ async function addRailwayServices(graph, services, scanPath) {
9047
9310
  nodesAdded += result.nodesAdded;
9048
9311
  edgesAdded += result.edgesAdded;
9049
9312
  };
9050
- add(import_types34.EdgeType.RUNS_ON, "railway", "railway");
9051
- add(import_types34.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
9052
- add(import_types34.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
9313
+ add(import_types36.EdgeType.RUNS_ON, "railway", "railway");
9314
+ add(import_types36.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
9315
+ add(import_types36.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
9053
9316
  }
9054
9317
  return { nodesAdded, edgesAdded };
9055
9318
  }
@@ -9057,12 +9320,12 @@ async function addRailwayServices(graph, services, scanPath) {
9057
9320
  // src/extract/infra/supabase.ts
9058
9321
  init_cjs_shims();
9059
9322
  var import_node_fs23 = require("fs");
9060
- var import_node_path47 = __toESM(require("path"), 1);
9323
+ var import_node_path48 = __toESM(require("path"), 1);
9061
9324
  var import_smol_toml4 = require("smol-toml");
9062
- var import_types35 = require("@neat.is/types");
9325
+ var import_types37 = require("@neat.is/types");
9063
9326
  async function readSupabaseConfig(dir) {
9064
- const relFile = import_node_path47.default.join("supabase", "config.toml");
9065
- const abs = import_node_path47.default.join(dir, relFile);
9327
+ const relFile = import_node_path48.default.join("supabase", "config.toml");
9328
+ const abs = import_node_path48.default.join(dir, relFile);
9066
9329
  if (!await exists(abs)) return null;
9067
9330
  const raw = await import_node_fs23.promises.readFile(abs, "utf8");
9068
9331
  const config = (0, import_smol_toml4.parse)(raw);
@@ -9076,7 +9339,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
9076
9339
  try {
9077
9340
  read = await readSupabaseConfig(service.dir);
9078
9341
  } catch (err) {
9079
- recordExtractionError("infra supabase", import_node_path47.default.relative(scanPath, service.dir), err);
9342
+ recordExtractionError("infra supabase", import_node_path48.default.relative(scanPath, service.dir), err);
9080
9343
  continue;
9081
9344
  }
9082
9345
  if (!read) continue;
@@ -9091,7 +9354,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
9091
9354
  });
9092
9355
  }
9093
9356
  const anchorId = service.node.id;
9094
- const evidenceFile = toPosix(import_node_path47.default.relative(scanPath, import_node_path47.default.join(service.dir, relFile)));
9357
+ const evidenceFile = toPosix(import_node_path48.default.relative(scanPath, import_node_path48.default.join(service.dir, relFile)));
9095
9358
  const add = (edgeType, kind, name) => {
9096
9359
  if (!name) return;
9097
9360
  const result = emitPlatformResourceEdge(
@@ -9107,10 +9370,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
9107
9370
  nodesAdded += result.nodesAdded;
9108
9371
  edgesAdded += result.edgesAdded;
9109
9372
  };
9110
- add(import_types35.EdgeType.RUNS_ON, "supabase", "supabase");
9111
- for (const fn of Object.keys(config.functions ?? {})) add(import_types35.EdgeType.DEPENDS_ON, "supabase-function", fn);
9112
- if (config.storage) add(import_types35.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
9113
- if (config.auth) add(import_types35.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
9373
+ add(import_types37.EdgeType.RUNS_ON, "supabase", "supabase");
9374
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types37.EdgeType.DEPENDS_ON, "supabase-function", fn);
9375
+ if (config.storage) add(import_types37.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
9376
+ if (config.auth) add(import_types37.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
9114
9377
  }
9115
9378
  return { nodesAdded, edgesAdded };
9116
9379
  }
@@ -9132,17 +9395,17 @@ async function addInfra(graph, scanPath, services) {
9132
9395
  }
9133
9396
 
9134
9397
  // src/extract/index.ts
9135
- var import_node_path49 = __toESM(require("path"), 1);
9398
+ var import_node_path50 = __toESM(require("path"), 1);
9136
9399
 
9137
9400
  // src/extract/retire.ts
9138
9401
  init_cjs_shims();
9139
9402
  var import_node_fs24 = require("fs");
9140
- var import_node_path48 = __toESM(require("path"), 1);
9141
- var import_types36 = require("@neat.is/types");
9403
+ var import_node_path49 = __toESM(require("path"), 1);
9404
+ var import_types38 = require("@neat.is/types");
9142
9405
  function dropOrphanedFileNodes(graph) {
9143
9406
  const orphans = [];
9144
9407
  graph.forEachNode((id, attrs) => {
9145
- if (attrs.type !== import_types36.NodeType.FileNode) return;
9408
+ if (attrs.type !== import_types38.NodeType.FileNode) return;
9146
9409
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
9147
9410
  orphans.push(id);
9148
9411
  }
@@ -9155,14 +9418,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
9155
9418
  const bases = [scanPath, ...serviceDirs];
9156
9419
  graph.forEachEdge((id, attrs) => {
9157
9420
  const edge = attrs;
9158
- if (edge.provenance !== import_types36.Provenance.EXTRACTED) return;
9421
+ if (edge.provenance !== import_types38.Provenance.EXTRACTED) return;
9159
9422
  const evidenceFile = edge.evidence?.file;
9160
9423
  if (!evidenceFile) return;
9161
- if (import_node_path48.default.isAbsolute(evidenceFile)) {
9424
+ if (import_node_path49.default.isAbsolute(evidenceFile)) {
9162
9425
  if (!(0, import_node_fs24.existsSync)(evidenceFile)) toDrop.push(id);
9163
9426
  return;
9164
9427
  }
9165
- const found = bases.some((base) => (0, import_node_fs24.existsSync)(import_node_path48.default.join(base, evidenceFile)));
9428
+ const found = bases.some((base) => (0, import_node_fs24.existsSync)(import_node_path49.default.join(base, evidenceFile)));
9166
9429
  if (!found) toDrop.push(id);
9167
9430
  });
9168
9431
  for (const id of toDrop) graph.dropEdge(id);
@@ -9215,7 +9478,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
9215
9478
  }
9216
9479
  const droppedEntries = drainDroppedExtracted();
9217
9480
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
9218
- const rejectedPath = import_node_path49.default.join(import_node_path49.default.dirname(opts.errorsPath), "rejected.ndjson");
9481
+ const rejectedPath = import_node_path50.default.join(import_node_path50.default.dirname(opts.errorsPath), "rejected.ndjson");
9219
9482
  try {
9220
9483
  await writeRejectedExtracted(droppedEntries, rejectedPath);
9221
9484
  } catch (err) {
@@ -9250,9 +9513,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
9250
9513
  // src/persist.ts
9251
9514
  init_cjs_shims();
9252
9515
  var import_node_fs25 = require("fs");
9253
- var import_node_path50 = __toESM(require("path"), 1);
9254
- var import_types37 = require("@neat.is/types");
9255
- var SCHEMA_VERSION = 5;
9516
+ var import_node_path51 = __toESM(require("path"), 1);
9517
+ var import_types39 = require("@neat.is/types");
9518
+ var SCHEMA_VERSION = 6;
9256
9519
  function migrateV1ToV2(payload) {
9257
9520
  const nodes = payload.graph.nodes;
9258
9521
  if (Array.isArray(nodes)) {
@@ -9270,18 +9533,30 @@ function migrateV3ToV4(payload) {
9270
9533
  function migrateV4ToV5(payload) {
9271
9534
  return { ...payload, schemaVersion: 5 };
9272
9535
  }
9536
+ function migrateV5ToV6(payload) {
9537
+ const nodes = payload.graph.nodes;
9538
+ if (Array.isArray(nodes)) {
9539
+ for (const node of nodes) {
9540
+ const attrs = node.attributes;
9541
+ if (!attrs || attrs.type !== import_types39.NodeType.InfraNode) continue;
9542
+ if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
9543
+ if (!Array.isArray(attrs.columns)) attrs.columns = [];
9544
+ }
9545
+ }
9546
+ return { ...payload, schemaVersion: 6 };
9547
+ }
9273
9548
  function migrateV2ToV3(payload) {
9274
9549
  const edges = payload.graph.edges;
9275
9550
  if (Array.isArray(edges)) {
9276
9551
  for (const edge of edges) {
9277
9552
  const attrs = edge.attributes;
9278
9553
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
9279
- attrs.provenance = import_types37.Provenance.OBSERVED;
9554
+ attrs.provenance = import_types39.Provenance.OBSERVED;
9280
9555
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
9281
9556
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
9282
9557
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
9283
9558
  if (type && source && target) {
9284
- const newId = (0, import_types37.observedEdgeId)(source, target, type);
9559
+ const newId = (0, import_types39.observedEdgeId)(source, target, type);
9285
9560
  attrs.id = newId;
9286
9561
  if (edge.key) edge.key = newId;
9287
9562
  }
@@ -9290,7 +9565,7 @@ function migrateV2ToV3(payload) {
9290
9565
  return { ...payload, schemaVersion: 3 };
9291
9566
  }
9292
9567
  async function ensureDir(filePath) {
9293
- await import_node_fs25.promises.mkdir(import_node_path50.default.dirname(filePath), { recursive: true });
9568
+ await import_node_fs25.promises.mkdir(import_node_path51.default.dirname(filePath), { recursive: true });
9294
9569
  }
9295
9570
  async function saveGraphToDisk(graph, outPath) {
9296
9571
  await ensureDir(outPath);
@@ -9324,6 +9599,9 @@ async function loadGraphFromDisk(graph, outPath) {
9324
9599
  if (payload.schemaVersion === 4) {
9325
9600
  payload = migrateV4ToV5(payload);
9326
9601
  }
9602
+ if (payload.schemaVersion === 5) {
9603
+ payload = migrateV5ToV6(payload);
9604
+ }
9327
9605
  if (payload.schemaVersion !== SCHEMA_VERSION) {
9328
9606
  throw new Error(
9329
9607
  `persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`
@@ -9374,23 +9652,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
9374
9652
 
9375
9653
  // src/projects.ts
9376
9654
  init_cjs_shims();
9377
- var import_node_path51 = __toESM(require("path"), 1);
9655
+ var import_node_path52 = __toESM(require("path"), 1);
9378
9656
  function pathsForProject(project, baseDir) {
9379
9657
  if (project === DEFAULT_PROJECT) {
9380
9658
  return {
9381
- snapshotPath: import_node_path51.default.join(baseDir, "graph.json"),
9382
- errorsPath: import_node_path51.default.join(baseDir, "errors.ndjson"),
9383
- staleEventsPath: import_node_path51.default.join(baseDir, "stale-events.ndjson"),
9384
- embeddingsCachePath: import_node_path51.default.join(baseDir, "embeddings.json"),
9385
- policyViolationsPath: import_node_path51.default.join(baseDir, "policy-violations.ndjson")
9659
+ snapshotPath: import_node_path52.default.join(baseDir, "graph.json"),
9660
+ errorsPath: import_node_path52.default.join(baseDir, "errors.ndjson"),
9661
+ staleEventsPath: import_node_path52.default.join(baseDir, "stale-events.ndjson"),
9662
+ embeddingsCachePath: import_node_path52.default.join(baseDir, "embeddings.json"),
9663
+ policyViolationsPath: import_node_path52.default.join(baseDir, "policy-violations.ndjson")
9386
9664
  };
9387
9665
  }
9388
9666
  return {
9389
- snapshotPath: import_node_path51.default.join(baseDir, `${project}.json`),
9390
- errorsPath: import_node_path51.default.join(baseDir, `errors.${project}.ndjson`),
9391
- staleEventsPath: import_node_path51.default.join(baseDir, `stale-events.${project}.ndjson`),
9392
- embeddingsCachePath: import_node_path51.default.join(baseDir, `embeddings.${project}.json`),
9393
- policyViolationsPath: import_node_path51.default.join(baseDir, `policy-violations.${project}.ndjson`)
9667
+ snapshotPath: import_node_path52.default.join(baseDir, `${project}.json`),
9668
+ errorsPath: import_node_path52.default.join(baseDir, `errors.${project}.ndjson`),
9669
+ staleEventsPath: import_node_path52.default.join(baseDir, `stale-events.${project}.ndjson`),
9670
+ embeddingsCachePath: import_node_path52.default.join(baseDir, `embeddings.${project}.json`),
9671
+ policyViolationsPath: import_node_path52.default.join(baseDir, `policy-violations.${project}.ndjson`)
9394
9672
  };
9395
9673
  }
9396
9674
  var Projects = class {
@@ -9428,19 +9706,19 @@ var Projects = class {
9428
9706
  init_cjs_shims();
9429
9707
  var import_fastify2 = __toESM(require("fastify"), 1);
9430
9708
  var import_cors = __toESM(require("@fastify/cors"), 1);
9431
- var import_types56 = require("@neat.is/types");
9709
+ var import_types58 = require("@neat.is/types");
9432
9710
 
9433
9711
  // src/extend/index.ts
9434
9712
  init_cjs_shims();
9435
9713
  var import_node_fs27 = require("fs");
9436
- var import_node_path53 = __toESM(require("path"), 1);
9714
+ var import_node_path54 = __toESM(require("path"), 1);
9437
9715
  var import_node_os2 = __toESM(require("os"), 1);
9438
9716
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
9439
9717
 
9440
9718
  // src/installers/package-manager.ts
9441
9719
  init_cjs_shims();
9442
9720
  var import_node_fs26 = require("fs");
9443
- var import_node_path52 = __toESM(require("path"), 1);
9721
+ var import_node_path53 = __toESM(require("path"), 1);
9444
9722
  var import_node_child_process = require("child_process");
9445
9723
  var LOCKFILE_PRIORITY = [
9446
9724
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -9462,22 +9740,22 @@ async function exists2(p) {
9462
9740
  }
9463
9741
  }
9464
9742
  async function detectPackageManager(serviceDir) {
9465
- let dir = import_node_path52.default.resolve(serviceDir);
9743
+ let dir = import_node_path53.default.resolve(serviceDir);
9466
9744
  const stops = /* @__PURE__ */ new Set();
9467
9745
  for (let i = 0; i < 64; i++) {
9468
9746
  if (stops.has(dir)) break;
9469
9747
  stops.add(dir);
9470
9748
  for (const candidate of LOCKFILE_PRIORITY) {
9471
- const lockPath = import_node_path52.default.join(dir, candidate.lockfile);
9749
+ const lockPath = import_node_path53.default.join(dir, candidate.lockfile);
9472
9750
  if (await exists2(lockPath)) {
9473
9751
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
9474
9752
  }
9475
9753
  }
9476
- const parent = import_node_path52.default.dirname(dir);
9754
+ const parent = import_node_path53.default.dirname(dir);
9477
9755
  if (parent === dir) break;
9478
9756
  dir = parent;
9479
9757
  }
9480
- return { pm: "npm", cwd: import_node_path52.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
9758
+ return { pm: "npm", cwd: import_node_path53.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
9481
9759
  }
9482
9760
  async function runPackageManagerInstall(cmd) {
9483
9761
  return new Promise((resolve) => {
@@ -9526,7 +9804,7 @@ async function fileExists2(p) {
9526
9804
  }
9527
9805
  }
9528
9806
  async function readPackageJson(scanPath) {
9529
- const pkgPath = import_node_path53.default.join(scanPath, "package.json");
9807
+ const pkgPath = import_node_path54.default.join(scanPath, "package.json");
9530
9808
  const raw = await import_node_fs27.promises.readFile(pkgPath, "utf8");
9531
9809
  return JSON.parse(raw);
9532
9810
  }
@@ -9545,11 +9823,11 @@ async function findHookFiles(scanPath) {
9545
9823
  for (const entry2 of entries) {
9546
9824
  if (entry2.isDirectory()) {
9547
9825
  if (entry2.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry2.name)) continue;
9548
- await walk6(import_node_path53.default.join(dir, entry2.name));
9826
+ await walk6(import_node_path54.default.join(dir, entry2.name));
9549
9827
  } else if (entry2.isFile()) {
9550
9828
  if ((entry2.name.startsWith("instrumentation") || entry2.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry2.name)) {
9551
- const rel = import_node_path53.default.relative(scanPath, import_node_path53.default.join(dir, entry2.name));
9552
- found.push(rel.split(import_node_path53.default.sep).join("/"));
9829
+ const rel = import_node_path54.default.relative(scanPath, import_node_path54.default.join(dir, entry2.name));
9830
+ found.push(rel.split(import_node_path54.default.sep).join("/"));
9553
9831
  }
9554
9832
  }
9555
9833
  }
@@ -9560,7 +9838,7 @@ async function findHookFiles(scanPath) {
9560
9838
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
9561
9839
  let fallback = null;
9562
9840
  for (const file of hookFiles) {
9563
- const content = await import_node_fs27.promises.readFile(import_node_path53.default.join(scanPath, file), "utf8");
9841
+ const content = await import_node_fs27.promises.readFile(import_node_path54.default.join(scanPath, file), "utf8");
9564
9842
  const patched = splicedContent(content, snippet2);
9565
9843
  if (patched !== null) return { file, content, patched };
9566
9844
  if (fallback === null) fallback = { file, content };
@@ -9568,11 +9846,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
9568
9846
  return { file: fallback.file, content: fallback.content, patched: null };
9569
9847
  }
9570
9848
  function extendLogPath() {
9571
- return process.env.NEAT_EXTEND_LOG ?? import_node_path53.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
9849
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path54.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
9572
9850
  }
9573
9851
  async function appendExtendLog(entry2) {
9574
9852
  const logPath = extendLogPath();
9575
- await import_node_fs27.promises.mkdir(import_node_path53.default.dirname(logPath), { recursive: true });
9853
+ await import_node_fs27.promises.mkdir(import_node_path54.default.dirname(logPath), { recursive: true });
9576
9854
  await import_node_fs27.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
9577
9855
  }
9578
9856
  function splicedContent(fileContent, snippet2) {
@@ -9631,7 +9909,7 @@ function lookupInstrumentation(library, installedVersion) {
9631
9909
  }
9632
9910
  async function describeProjectInstrumentation(ctx) {
9633
9911
  const hookFiles = await findHookFiles(ctx.scanPath);
9634
- const envNeat = await fileExists2(import_node_path53.default.join(ctx.scanPath, ".env.neat"));
9912
+ const envNeat = await fileExists2(import_node_path54.default.join(ctx.scanPath, ".env.neat"));
9635
9913
  const registryInstrPackages = new Set(
9636
9914
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
9637
9915
  );
@@ -9653,7 +9931,7 @@ async function applyExtension(ctx, args, options) {
9653
9931
  );
9654
9932
  }
9655
9933
  for (const file of hookFiles) {
9656
- const content = await import_node_fs27.promises.readFile(import_node_path53.default.join(ctx.scanPath, file), "utf8");
9934
+ const content = await import_node_fs27.promises.readFile(import_node_path54.default.join(ctx.scanPath, file), "utf8");
9657
9935
  if (content.includes(args.registration_snippet)) {
9658
9936
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
9659
9937
  }
@@ -9665,10 +9943,10 @@ async function applyExtension(ctx, args, options) {
9665
9943
  );
9666
9944
  }
9667
9945
  const primaryFile = primary.file;
9668
- const primaryPath = import_node_path53.default.join(ctx.scanPath, primaryFile);
9946
+ const primaryPath = import_node_path54.default.join(ctx.scanPath, primaryFile);
9669
9947
  const filesTouched = [];
9670
9948
  const depsAdded = [];
9671
- const pkgPath = import_node_path53.default.join(ctx.scanPath, "package.json");
9949
+ const pkgPath = import_node_path54.default.join(ctx.scanPath, "package.json");
9672
9950
  const pkg = await readPackageJson(ctx.scanPath);
9673
9951
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
9674
9952
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -9707,7 +9985,7 @@ async function dryRunExtension(ctx, args) {
9707
9985
  };
9708
9986
  }
9709
9987
  for (const file of hookFiles) {
9710
- const content = await import_node_fs27.promises.readFile(import_node_path53.default.join(ctx.scanPath, file), "utf8");
9988
+ const content = await import_node_fs27.promises.readFile(import_node_path54.default.join(ctx.scanPath, file), "utf8");
9711
9989
  if (content.includes(args.registration_snippet)) {
9712
9990
  return {
9713
9991
  library: args.library,
@@ -9748,7 +10026,7 @@ async function rollbackExtension(ctx, args) {
9748
10026
  if (!match) {
9749
10027
  return { undone: false, message: "no apply found for library" };
9750
10028
  }
9751
- const pkgPath = import_node_path53.default.join(ctx.scanPath, "package.json");
10029
+ const pkgPath = import_node_path54.default.join(ctx.scanPath, "package.json");
9752
10030
  if (await fileExists2(pkgPath)) {
9753
10031
  const pkg = await readPackageJson(ctx.scanPath);
9754
10032
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -9759,7 +10037,7 @@ async function rollbackExtension(ctx, args) {
9759
10037
  }
9760
10038
  const hookFiles = await findHookFiles(ctx.scanPath);
9761
10039
  for (const file of hookFiles) {
9762
- const filePath = import_node_path53.default.join(ctx.scanPath, file);
10040
+ const filePath = import_node_path54.default.join(ctx.scanPath, file);
9763
10041
  const content = await import_node_fs27.promises.readFile(filePath, "utf8");
9764
10042
  if (content.includes(match.registration_snippet)) {
9765
10043
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -9775,39 +10053,39 @@ async function rollbackExtension(ctx, args) {
9775
10053
 
9776
10054
  // src/divergences.ts
9777
10055
  init_cjs_shims();
9778
- var import_types38 = require("@neat.is/types");
10056
+ var import_types40 = require("@neat.is/types");
9779
10057
  function bucketKey(source, target, type) {
9780
10058
  return `${type}|${source}|${target}`;
9781
10059
  }
9782
10060
  function bucketSourceFor(graph, edge) {
9783
- if (edge.type !== import_types38.EdgeType.CONNECTS_TO) return edge.source;
9784
- const parsed = (0, import_types38.parseFileId)(edge.source);
10061
+ if (edge.type !== import_types40.EdgeType.CONNECTS_TO) return edge.source;
10062
+ const parsed = (0, import_types40.parseFileId)(edge.source);
9785
10063
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
9786
10064
  const target = graph.getNodeAttributes(edge.target);
9787
- if (target.type !== import_types38.NodeType.DatabaseNode) return edge.source;
9788
- return (0, import_types38.serviceId)(parsed.service);
10065
+ if (target.type !== import_types40.NodeType.DatabaseNode) return edge.source;
10066
+ return (0, import_types40.serviceId)(parsed.service);
9789
10067
  }
9790
10068
  function bucketEdges(graph) {
9791
10069
  const buckets2 = /* @__PURE__ */ new Map();
9792
10070
  graph.forEachEdge((id, attrs) => {
9793
10071
  const e = attrs;
9794
- const parsed = (0, import_types38.parseEdgeId)(id);
10072
+ const parsed = (0, import_types40.parseEdgeId)(id);
9795
10073
  const provenance = parsed?.provenance ?? e.provenance;
9796
10074
  const source = bucketSourceFor(graph, e);
9797
10075
  const key = bucketKey(source, e.target, e.type);
9798
10076
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
9799
10077
  switch (provenance) {
9800
- case import_types38.Provenance.EXTRACTED:
10078
+ case import_types40.Provenance.EXTRACTED:
9801
10079
  cur.extracted = e;
9802
10080
  break;
9803
- case import_types38.Provenance.OBSERVED:
10081
+ case import_types40.Provenance.OBSERVED:
9804
10082
  cur.observed = e;
9805
10083
  break;
9806
- case import_types38.Provenance.INFERRED:
10084
+ case import_types40.Provenance.INFERRED:
9807
10085
  cur.inferred = e;
9808
10086
  break;
9809
10087
  default:
9810
- if (e.provenance === import_types38.Provenance.STALE) cur.stale = e;
10088
+ if (e.provenance === import_types40.Provenance.STALE) cur.stale = e;
9811
10089
  }
9812
10090
  buckets2.set(key, cur);
9813
10091
  });
@@ -9816,17 +10094,17 @@ function bucketEdges(graph) {
9816
10094
  function nodeIsFrontier(graph, nodeId) {
9817
10095
  if (!graph.hasNode(nodeId)) return false;
9818
10096
  const attrs = graph.getNodeAttributes(nodeId);
9819
- return attrs.type === import_types38.NodeType.FrontierNode;
10097
+ return attrs.type === import_types40.NodeType.FrontierNode;
9820
10098
  }
9821
10099
  function nodeIsWebsocketChannel(graph, nodeId) {
9822
10100
  if (!graph.hasNode(nodeId)) return false;
9823
10101
  const attrs = graph.getNodeAttributes(nodeId);
9824
- return attrs.type === import_types38.NodeType.WebSocketChannelNode;
10102
+ return attrs.type === import_types40.NodeType.WebSocketChannelNode;
9825
10103
  }
9826
10104
  function nodeIsSymbol(graph, nodeId) {
9827
10105
  if (!graph.hasNode(nodeId)) return false;
9828
10106
  const attrs = graph.getNodeAttributes(nodeId);
9829
- return attrs.type === import_types38.NodeType.SymbolNode;
10107
+ return attrs.type === import_types40.NodeType.SymbolNode;
9830
10108
  }
9831
10109
  function clampConfidence(n) {
9832
10110
  if (!Number.isFinite(n)) return 0;
@@ -9846,14 +10124,14 @@ function gradedConfidence(edge) {
9846
10124
  return clampConfidence(confidenceForEdge(edge));
9847
10125
  }
9848
10126
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
9849
- import_types38.EdgeType.CALLS,
9850
- import_types38.EdgeType.CONNECTS_TO,
9851
- import_types38.EdgeType.PUBLISHES_TO,
9852
- import_types38.EdgeType.CONSUMES_FROM
10127
+ import_types40.EdgeType.CALLS,
10128
+ import_types40.EdgeType.CONNECTS_TO,
10129
+ import_types40.EdgeType.PUBLISHES_TO,
10130
+ import_types40.EdgeType.CONSUMES_FROM
9853
10131
  ]);
9854
10132
  function detectMissingDivergences(graph, bucket) {
9855
10133
  const out = [];
9856
- if (bucket.type === import_types38.EdgeType.CONTAINS) return out;
10134
+ if (bucket.type === import_types40.EdgeType.CONTAINS) return out;
9857
10135
  if (nodeIsSymbol(graph, bucket.source) || nodeIsSymbol(graph, bucket.target)) return out;
9858
10136
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
9859
10137
  if (!nodeIsFrontier(graph, bucket.target)) {
@@ -9895,7 +10173,7 @@ function declaredHostFor(svc) {
9895
10173
  function hasExtractedConfiguredBy(graph, svcId) {
9896
10174
  for (const edgeId of graph.outboundEdges(svcId)) {
9897
10175
  const e = graph.getEdgeAttributes(edgeId);
9898
- if (e.type === import_types38.EdgeType.CONFIGURED_BY && e.provenance === import_types38.Provenance.EXTRACTED) {
10176
+ if (e.type === import_types40.EdgeType.CONFIGURED_BY && e.provenance === import_types40.Provenance.EXTRACTED) {
9899
10177
  return true;
9900
10178
  }
9901
10179
  }
@@ -9908,10 +10186,10 @@ function detectHostMismatch(graph, svcId, svc) {
9908
10186
  const out = [];
9909
10187
  for (const edgeId of graph.outboundEdges(svcId)) {
9910
10188
  const edge = graph.getEdgeAttributes(edgeId);
9911
- if (edge.type !== import_types38.EdgeType.CONNECTS_TO) continue;
9912
- if (edge.provenance !== import_types38.Provenance.OBSERVED) continue;
10189
+ if (edge.type !== import_types40.EdgeType.CONNECTS_TO) continue;
10190
+ if (edge.provenance !== import_types40.Provenance.OBSERVED) continue;
9913
10191
  const target = graph.getNodeAttributes(edge.target);
9914
- if (target.type !== import_types38.NodeType.DatabaseNode) continue;
10192
+ if (target.type !== import_types40.NodeType.DatabaseNode) continue;
9915
10193
  const observedHost = target.host?.trim();
9916
10194
  if (!observedHost) continue;
9917
10195
  if (observedHost === declaredHost) continue;
@@ -9933,10 +10211,10 @@ function detectCompatDivergences(graph, svcId, svc) {
9933
10211
  const deps = svc.dependencies ?? {};
9934
10212
  for (const edgeId of graph.outboundEdges(svcId)) {
9935
10213
  const edge = graph.getEdgeAttributes(edgeId);
9936
- if (edge.type !== import_types38.EdgeType.CONNECTS_TO) continue;
9937
- if (edge.provenance !== import_types38.Provenance.OBSERVED) continue;
10214
+ if (edge.type !== import_types40.EdgeType.CONNECTS_TO) continue;
10215
+ if (edge.provenance !== import_types40.Provenance.OBSERVED) continue;
9938
10216
  const target = graph.getNodeAttributes(edge.target);
9939
- if (target.type !== import_types38.NodeType.DatabaseNode) continue;
10217
+ if (target.type !== import_types40.NodeType.DatabaseNode) continue;
9940
10218
  for (const pair of compatPairs()) {
9941
10219
  if (pair.engine !== target.engine) continue;
9942
10220
  const declared = deps[pair.driver];
@@ -9986,6 +10264,44 @@ function detectCompatDivergences(graph, svcId, svc) {
9986
10264
  }
9987
10265
  return out;
9988
10266
  }
10267
+ var RECOMMENDATION_COLUMN_MISSING_OBSERVED = "Verify the column is exercised in production; a migration that renamed or dropped it may have left a writer declaring the old name.";
10268
+ var RECOMMENDATION_COLUMN_MISSING_EXTRACTED = "The schema or migration is likely behind the code \u2014 production writes a column the declared schema does not carry. Check for a field rename that updated the query but not the model.";
10269
+ function detectColumnDrift(node) {
10270
+ const columns = node.columns;
10271
+ if (!columns || columns.length === 0) return [];
10272
+ const anyDeclared = columns.some(columnIsDeclared);
10273
+ const anyObserved = columns.some(columnIsObserved);
10274
+ if (!anyDeclared || !anyObserved) return [];
10275
+ const out = [];
10276
+ for (const col of columns) {
10277
+ const declared = columnIsDeclared(col);
10278
+ const observed = columnIsObserved(col);
10279
+ if (declared && !observed) {
10280
+ out.push({
10281
+ type: "missing-observed",
10282
+ source: node.id,
10283
+ target: node.id,
10284
+ table: node.id,
10285
+ column: col.name,
10286
+ confidence: clampConfidence(col.confidence),
10287
+ reason: `Schema declares column ${node.name}.${col.name} but no production statement has touched it.`,
10288
+ recommendation: RECOMMENDATION_COLUMN_MISSING_OBSERVED
10289
+ });
10290
+ } else if (observed && !declared) {
10291
+ out.push({
10292
+ type: "missing-extracted",
10293
+ source: node.id,
10294
+ target: node.id,
10295
+ table: node.id,
10296
+ column: col.name,
10297
+ confidence: clampConfidence(col.confidence),
10298
+ reason: `Production touched column ${node.name}.${col.name} but the schema does not declare it.`,
10299
+ recommendation: RECOMMENDATION_COLUMN_MISSING_EXTRACTED
10300
+ });
10301
+ }
10302
+ }
10303
+ return out;
10304
+ }
9989
10305
  function involvesNode(d, nodeId) {
9990
10306
  return d.source === nodeId || d.target === nodeId;
9991
10307
  }
@@ -9995,7 +10311,7 @@ function suppressHostMismatchHalves(all) {
9995
10311
  for (const d of all) {
9996
10312
  if (d.type !== "host-mismatch") continue;
9997
10313
  observedHalf.add(`${d.source}->${d.target}`);
9998
- declaredHalf.add((0, import_types38.databaseId)(d.extractedHost));
10314
+ declaredHalf.add((0, import_types40.databaseId)(d.extractedHost));
9999
10315
  }
10000
10316
  if (observedHalf.size === 0) return all;
10001
10317
  return all.filter((d) => {
@@ -10014,10 +10330,15 @@ function computeDivergences(graph, opts = {}) {
10014
10330
  }
10015
10331
  graph.forEachNode((nodeId, attrs) => {
10016
10332
  const n = attrs;
10017
- if (n.type !== import_types38.NodeType.ServiceNode) return;
10018
- const svc = n;
10019
- for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
10020
- for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
10333
+ if (n.type === import_types40.NodeType.ServiceNode) {
10334
+ const svc = n;
10335
+ for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
10336
+ for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
10337
+ return;
10338
+ }
10339
+ if (n.type === import_types40.NodeType.InfraNode && n.kind === "sql-table") {
10340
+ for (const d of detectColumnDrift(n)) all.push(d);
10341
+ }
10021
10342
  });
10022
10343
  const reconciled = suppressHostMismatchHalves(all);
10023
10344
  let filtered = reconciled;
@@ -10046,9 +10367,12 @@ function computeDivergences(graph, opts = {}) {
10046
10367
  if (lead !== 0) return lead;
10047
10368
  if (a.type !== b.type) return a.type.localeCompare(b.type);
10048
10369
  if (a.source !== b.source) return a.source.localeCompare(b.source);
10049
- return a.target.localeCompare(b.target);
10370
+ if (a.target !== b.target) return a.target.localeCompare(b.target);
10371
+ const ac = "column" in a && a.column ? a.column : "";
10372
+ const bc = "column" in b && b.column ? b.column : "";
10373
+ return ac.localeCompare(bc);
10050
10374
  });
10051
- return import_types38.DivergenceResultSchema.parse({
10375
+ return import_types40.DivergenceResultSchema.parse({
10052
10376
  divergences: filtered,
10053
10377
  totalAffected: filtered.length,
10054
10378
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -10181,26 +10505,26 @@ function canonicalJson(value) {
10181
10505
  init_cjs_shims();
10182
10506
  var import_node_fs29 = require("fs");
10183
10507
  var import_node_os3 = __toESM(require("os"), 1);
10184
- var import_node_path54 = __toESM(require("path"), 1);
10185
- var import_types39 = require("@neat.is/types");
10508
+ var import_node_path55 = __toESM(require("path"), 1);
10509
+ var import_types41 = require("@neat.is/types");
10186
10510
  var LOCK_TIMEOUT_MS = 5e3;
10187
10511
  var LOCK_RETRY_MS = 50;
10188
10512
  function neatHome() {
10189
10513
  const override = process.env.NEAT_HOME;
10190
- if (override && override.length > 0) return import_node_path54.default.resolve(override);
10191
- return import_node_path54.default.join(import_node_os3.default.homedir(), ".neat");
10514
+ if (override && override.length > 0) return import_node_path55.default.resolve(override);
10515
+ return import_node_path55.default.join(import_node_os3.default.homedir(), ".neat");
10192
10516
  }
10193
10517
  function registryPath() {
10194
- return import_node_path54.default.join(neatHome(), "projects.json");
10518
+ return import_node_path55.default.join(neatHome(), "projects.json");
10195
10519
  }
10196
10520
  function registryLockPath() {
10197
- return import_node_path54.default.join(neatHome(), "projects.json.lock");
10521
+ return import_node_path55.default.join(neatHome(), "projects.json.lock");
10198
10522
  }
10199
10523
  function daemonPidPath() {
10200
- return import_node_path54.default.join(neatHome(), "neatd.pid");
10524
+ return import_node_path55.default.join(neatHome(), "neatd.pid");
10201
10525
  }
10202
10526
  function daemonsDir() {
10203
- return import_node_path54.default.join(neatHome(), "daemons");
10527
+ return import_node_path55.default.join(neatHome(), "daemons");
10204
10528
  }
10205
10529
  function isFiniteInt(v) {
10206
10530
  return typeof v === "number" && Number.isFinite(v);
@@ -10241,7 +10565,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
10241
10565
  const out = [];
10242
10566
  for (const name of names) {
10243
10567
  if (!name.endsWith(".json")) continue;
10244
- const file = import_node_path54.default.join(dir, name);
10568
+ const file = import_node_path55.default.join(dir, name);
10245
10569
  let raw;
10246
10570
  try {
10247
10571
  raw = await import_node_fs29.promises.readFile(file, "utf8");
@@ -10318,7 +10642,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
10318
10642
  }
10319
10643
  }
10320
10644
  async function writeAtomically(target, contents) {
10321
- await import_node_fs29.promises.mkdir(import_node_path54.default.dirname(target), { recursive: true });
10645
+ await import_node_fs29.promises.mkdir(import_node_path55.default.dirname(target), { recursive: true });
10322
10646
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
10323
10647
  const fd = await import_node_fs29.promises.open(tmp, "w");
10324
10648
  try {
@@ -10331,7 +10655,7 @@ async function writeAtomically(target, contents) {
10331
10655
  }
10332
10656
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
10333
10657
  const deadline = Date.now() + timeoutMs;
10334
- await import_node_fs29.promises.mkdir(import_node_path54.default.dirname(lockPath), { recursive: true });
10658
+ await import_node_fs29.promises.mkdir(import_node_path55.default.dirname(lockPath), { recursive: true });
10335
10659
  let probedHolder = false;
10336
10660
  while (true) {
10337
10661
  try {
@@ -10384,10 +10708,10 @@ async function readRegistry() {
10384
10708
  throw err;
10385
10709
  }
10386
10710
  const parsed = JSON.parse(raw);
10387
- return import_types39.RegistryFileSchema.parse(parsed);
10711
+ return import_types41.RegistryFileSchema.parse(parsed);
10388
10712
  }
10389
10713
  async function writeRegistry(reg) {
10390
- const validated = import_types39.RegistryFileSchema.parse(reg);
10714
+ const validated = import_types41.RegistryFileSchema.parse(reg);
10391
10715
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
10392
10716
  }
10393
10717
  async function getProject(name) {
@@ -10532,7 +10856,7 @@ init_auth();
10532
10856
  // src/connectors-config.ts
10533
10857
  init_cjs_shims();
10534
10858
  var import_node_os4 = __toESM(require("os"), 1);
10535
- var import_node_path55 = __toESM(require("path"), 1);
10859
+ var import_node_path56 = __toESM(require("path"), 1);
10536
10860
  var import_node_fs30 = require("fs");
10537
10861
  var CONNECTORS_CONFIG_VERSION = 1;
10538
10862
  var EnvRefUnsetError = class extends Error {
@@ -10547,11 +10871,11 @@ var EnvRefUnsetError = class extends Error {
10547
10871
  };
10548
10872
  function neatHome2() {
10549
10873
  const override = process.env.NEAT_HOME;
10550
- if (override && override.length > 0) return import_node_path55.default.resolve(override);
10551
- return import_node_path55.default.join(import_node_os4.default.homedir(), ".neat");
10874
+ if (override && override.length > 0) return import_node_path56.default.resolve(override);
10875
+ return import_node_path56.default.join(import_node_os4.default.homedir(), ".neat");
10552
10876
  }
10553
10877
  function connectorsConfigPath(home = neatHome2()) {
10554
- return import_node_path55.default.join(home, "connectors.json");
10878
+ return import_node_path56.default.join(home, "connectors.json");
10555
10879
  }
10556
10880
  var MODE_MASK_LOOSER_THAN_0600 = 63;
10557
10881
  async function warnIfModeLooserThan0600(file) {
@@ -10738,15 +11062,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
10738
11062
 
10739
11063
  // src/connectors/index.ts
10740
11064
  init_cjs_shims();
10741
- var import_types40 = require("@neat.is/types");
11065
+ var import_types42 = require("@neat.is/types");
10742
11066
  var NO_ENV = "unknown";
10743
11067
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
10744
11068
  if (!graph.hasNode(targetNodeId)) return void 0;
10745
11069
  const sites = [];
10746
11070
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
10747
11071
  const edge = graph.getEdgeAttributes(edgeId);
10748
- if (edge.provenance !== import_types40.Provenance.EXTRACTED) continue;
10749
- const parsed = (0, import_types40.parseFileId)(edge.source);
11072
+ if (edge.provenance !== import_types42.Provenance.EXTRACTED) continue;
11073
+ const parsed = (0, import_types42.parseFileId)(edge.source);
10750
11074
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
10751
11075
  const site = { relPath: edge.evidence.file };
10752
11076
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -10757,7 +11081,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
10757
11081
  function routeCallSiteFor(graph, targetNodeId) {
10758
11082
  if (!graph.hasNode(targetNodeId)) return void 0;
10759
11083
  const attrs = graph.getNodeAttributes(targetNodeId);
10760
- if (attrs.type !== import_types40.NodeType.RouteNode || !attrs.path) return void 0;
11084
+ if (attrs.type !== import_types42.NodeType.RouteNode || !attrs.path) return void 0;
10761
11085
  const site = { relPath: attrs.path };
10762
11086
  if (attrs.line !== void 0) site.line = attrs.line;
10763
11087
  return site;
@@ -10777,6 +11101,7 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
10777
11101
  const { kind, name, provider } = resolved.ensureInfraNode;
10778
11102
  ensureInfraNode(graph, kind, name, provider);
10779
11103
  }
11104
+ mergeObservedColumns(graph, resolved.targetNodeId, signal.columns);
10780
11105
  const serviceNodeId = ensureServiceNode(graph, resolved.serviceName, NO_ENV);
10781
11106
  const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } : routeCallSiteFor(graph, resolved.targetNodeId) ?? staticCallSiteFor(graph, resolved.serviceName, resolved.targetNodeId);
10782
11107
  const sourceId = callSite ? ensureObservedFileNode(graph, resolved.serviceName, serviceNodeId, callSite) : serviceNodeId;
@@ -11207,6 +11532,7 @@ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl
11207
11532
 
11208
11533
  // src/connectors/supabase/map.ts
11209
11534
  init_cjs_shims();
11535
+ init_otel();
11210
11536
 
11211
11537
  // src/connectors/supabase/types.ts
11212
11538
  init_cjs_shims();
@@ -11232,10 +11558,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
11232
11558
  // src/connectors/supabase/map.ts
11233
11559
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
11234
11560
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
11235
- function targetFromRestPath(path60) {
11236
- const rpcMatch = REST_RPC_PATH_RE.exec(path60);
11561
+ function targetFromRestPath(path61) {
11562
+ const rpcMatch = REST_RPC_PATH_RE.exec(path61);
11237
11563
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
11238
- const tableMatch = REST_TABLE_PATH_RE.exec(path60);
11564
+ const tableMatch = REST_TABLE_PATH_RE.exec(path61);
11239
11565
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
11240
11566
  return null;
11241
11567
  }
@@ -11299,12 +11625,14 @@ function diffPgStatStatementsToSignals(rows, previous, nowIso2) {
11299
11625
  if (delta <= 0) continue;
11300
11626
  const table = tableNameFromQueryText(row.query);
11301
11627
  if (!table) continue;
11628
+ const columns = columnsFromSqlStatement(row.query);
11302
11629
  signals.push({
11303
11630
  targetKind: SUPABASE_TABLE_TARGET_KIND,
11304
11631
  targetName: table,
11305
11632
  callCount: delta,
11306
11633
  errorCount: 0,
11307
- lastObservedIso: nowIso2
11634
+ lastObservedIso: nowIso2,
11635
+ ...columns.length > 0 ? { columns } : {}
11308
11636
  });
11309
11637
  }
11310
11638
  for (const queryid of [...previous.keys()]) {
@@ -11344,23 +11672,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
11344
11672
 
11345
11673
  // src/connectors/supabase/resolve.ts
11346
11674
  init_cjs_shims();
11347
- var import_types42 = require("@neat.is/types");
11675
+ var import_types44 = require("@neat.is/types");
11348
11676
  function createSupabaseResolveTarget(graph, config) {
11349
11677
  return (signal, _ctx) => {
11350
11678
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
11351
11679
  return null;
11352
11680
  }
11353
- const subResourceId = (0, import_types42.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
11681
+ const subResourceId = (0, import_types44.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
11354
11682
  if (graph.hasNode(subResourceId)) {
11355
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types42.EdgeType.CALLS };
11683
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types44.EdgeType.CALLS };
11356
11684
  }
11357
- const bareResourceId = (0, import_types42.infraId)(signal.targetKind, signal.targetName);
11685
+ const bareResourceId = (0, import_types44.infraId)(signal.targetKind, signal.targetName);
11358
11686
  if (graph.hasNode(bareResourceId)) {
11359
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types42.EdgeType.CALLS };
11687
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types44.EdgeType.CALLS };
11360
11688
  }
11361
- const projectLevelId = (0, import_types42.infraId)("supabase", config.nodeRef);
11689
+ const projectLevelId = (0, import_types44.infraId)("supabase", config.nodeRef);
11362
11690
  if (graph.hasNode(projectLevelId)) {
11363
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types42.EdgeType.CALLS };
11691
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types44.EdgeType.CALLS };
11364
11692
  }
11365
11693
  return null;
11366
11694
  };
@@ -11453,7 +11781,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
11453
11781
 
11454
11782
  // src/connectors/railway/index.ts
11455
11783
  init_cjs_shims();
11456
- var import_types46 = require("@neat.is/types");
11784
+ var import_types48 = require("@neat.is/types");
11457
11785
 
11458
11786
  // src/connectors/railway/client.ts
11459
11787
  init_cjs_shims();
@@ -11604,7 +11932,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
11604
11932
  const out = [];
11605
11933
  graph.forEachNode((_id, attrs) => {
11606
11934
  const node = attrs;
11607
- if (node.type !== import_types46.NodeType.RouteNode) return;
11935
+ if (node.type !== import_types48.NodeType.RouteNode) return;
11608
11936
  const route = attrs;
11609
11937
  if (route.service !== serviceName) return;
11610
11938
  out.push({
@@ -11708,12 +12036,12 @@ function createRailwayResolveTarget(config) {
11708
12036
  const serviceName = config.serviceNameById[config.serviceId];
11709
12037
  if (!serviceName) return null;
11710
12038
  if (signal.targetKind === ROUTE_TARGET_KIND) {
11711
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types46.EdgeType.CALLS };
12039
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types48.EdgeType.CALLS };
11712
12040
  }
11713
12041
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
11714
12042
  const peerName = config.serviceNameById[signal.targetName];
11715
12043
  if (!peerName) return null;
11716
- return { targetNodeId: (0, import_types46.serviceId)(peerName), serviceName, edgeType: import_types46.EdgeType.CONNECTS_TO };
12044
+ return { targetNodeId: (0, import_types48.serviceId)(peerName), serviceName, edgeType: import_types48.EdgeType.CONNECTS_TO };
11717
12045
  }
11718
12046
  return null;
11719
12047
  };
@@ -11837,9 +12165,9 @@ function parseFirebaseTargetName(targetName) {
11837
12165
  const secondSep = rest.indexOf(FIELD_SEP);
11838
12166
  if (secondSep === -1) return null;
11839
12167
  const method = rest.slice(0, secondSep);
11840
- const path60 = rest.slice(secondSep + 1);
11841
- if (!resourceName || !method || !path60) return null;
11842
- return { resourceName, method, path: path60 };
12168
+ const path61 = rest.slice(secondSep + 1);
12169
+ if (!resourceName || !method || !path61) return null;
12170
+ return { resourceName, method, path: path61 };
11843
12171
  }
11844
12172
  function resourceNameFor(type, labels) {
11845
12173
  if (!labels) return null;
@@ -11877,14 +12205,14 @@ function mapLogEntryToSignal(entry2) {
11877
12205
  if (!req2) return null;
11878
12206
  if (typeof req2.requestMethod !== "string" || req2.requestMethod.length === 0) return null;
11879
12207
  const method = req2.requestMethod.toUpperCase();
11880
- const path60 = pathFromRequestUrl(req2.requestUrl);
11881
- if (path60 === null) return null;
12208
+ const path61 = pathFromRequestUrl(req2.requestUrl);
12209
+ if (path61 === null) return null;
11882
12210
  const timestamp = entry2.timestamp;
11883
12211
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
11884
12212
  const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD2;
11885
12213
  return {
11886
12214
  targetKind: resourceType,
11887
- targetName: packFirebaseTargetName({ resourceName, method, path: path60 }),
12215
+ targetName: packFirebaseTargetName({ resourceName, method, path: path61 }),
11888
12216
  callCount: 1,
11889
12217
  errorCount: isError ? 1 : 0,
11890
12218
  lastObservedIso: timestamp
@@ -11901,7 +12229,7 @@ function mapLogEntriesToSignals(entries) {
11901
12229
 
11902
12230
  // src/connectors/firebase/resolve.ts
11903
12231
  init_cjs_shims();
11904
- var import_types47 = require("@neat.is/types");
12232
+ var import_types49 = require("@neat.is/types");
11905
12233
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
11906
12234
  switch (resourceType) {
11907
12235
  case "cloud_function":
@@ -11916,7 +12244,7 @@ function routeEntriesFor(graph, serviceName) {
11916
12244
  const entries = [];
11917
12245
  graph.forEachNode((_id, attrs) => {
11918
12246
  const node = attrs;
11919
- if (node.type !== import_types47.NodeType.RouteNode) return;
12247
+ if (node.type !== import_types49.NodeType.RouteNode) return;
11920
12248
  const route = attrs;
11921
12249
  if (route.service !== serviceName) return;
11922
12250
  entries.push({
@@ -11948,7 +12276,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
11948
12276
  return {
11949
12277
  targetNodeId: match.routeNodeId,
11950
12278
  serviceName,
11951
- edgeType: import_types47.EdgeType.CALLS
12279
+ edgeType: import_types49.EdgeType.CALLS
11952
12280
  };
11953
12281
  };
11954
12282
  }
@@ -11975,7 +12303,7 @@ init_cjs_shims();
11975
12303
 
11976
12304
  // src/connectors/cloudflare/connector.ts
11977
12305
  init_cjs_shims();
11978
- var import_types49 = require("@neat.is/types");
12306
+ var import_types51 = require("@neat.is/types");
11979
12307
 
11980
12308
  // src/connectors/cloudflare/client.ts
11981
12309
  init_cjs_shims();
@@ -12091,7 +12419,7 @@ function mapEventToSignal(event) {
12091
12419
  if (Number.isNaN(observedAt.getTime())) return null;
12092
12420
  const statusCode = metadata?.statusCode;
12093
12421
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
12094
- const path60 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
12422
+ const path61 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
12095
12423
  return {
12096
12424
  targetKind: CLOUDFLARE_TARGET_KIND,
12097
12425
  targetName: scriptName,
@@ -12099,7 +12427,7 @@ function mapEventToSignal(event) {
12099
12427
  errorCount: isError ? 1 : 0,
12100
12428
  lastObservedIso: observedAt.toISOString(),
12101
12429
  method,
12102
- ...path60 ? { path: path60 } : {},
12430
+ ...path61 ? { path: path61 } : {},
12103
12431
  ...typeof statusCode === "number" ? { statusCode } : {},
12104
12432
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
12105
12433
  };
@@ -12139,19 +12467,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
12139
12467
  graph.forEachNode((id, attrs) => {
12140
12468
  if (found) return;
12141
12469
  const a = attrs;
12142
- if (a.type === import_types49.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
12470
+ if (a.type === import_types51.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
12143
12471
  found = id;
12144
12472
  }
12145
12473
  });
12146
12474
  return found;
12147
12475
  }
12148
- function findMatchingRouteNode(graph, serviceName, method, path60) {
12149
- const normalizedPath = normalizePathTemplate(path60);
12476
+ function findMatchingRouteNode(graph, serviceName, method, path61) {
12477
+ const normalizedPath = normalizePathTemplate(path61);
12150
12478
  let found = null;
12151
12479
  graph.forEachNode((id, attrs) => {
12152
12480
  if (found) return;
12153
12481
  const a = attrs;
12154
- if (a.type !== import_types49.NodeType.RouteNode || a.service !== serviceName) return;
12482
+ if (a.type !== import_types51.NodeType.RouteNode || a.service !== serviceName) return;
12155
12483
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
12156
12484
  const routeMethod = (a.method ?? "").toUpperCase();
12157
12485
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -12163,18 +12491,18 @@ function createCloudflareResolveTarget(config, graph) {
12163
12491
  return (signal) => {
12164
12492
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
12165
12493
  const scriptName = signal.targetName;
12166
- const { method, path: path60 } = signal;
12494
+ const { method, path: path61 } = signal;
12167
12495
  const resolveRouteGrain = (serviceName, wholeFileId) => {
12168
- if (!method || !path60) return wholeFileId;
12169
- return findMatchingRouteNode(graph, serviceName, method, path60) ?? wholeFileId;
12496
+ if (!method || !path61) return wholeFileId;
12497
+ return findMatchingRouteNode(graph, serviceName, method, path61) ?? wholeFileId;
12170
12498
  };
12171
12499
  const mapping = config.workers?.[scriptName];
12172
12500
  if (mapping) {
12173
- const wholeFileId = (0, import_types49.fileId)(mapping.service, mapping.entryFile);
12501
+ const wholeFileId = (0, import_types51.fileId)(mapping.service, mapping.entryFile);
12174
12502
  return {
12175
12503
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
12176
12504
  serviceName: mapping.service,
12177
- edgeType: import_types49.EdgeType.CALLS
12505
+ edgeType: import_types51.EdgeType.CALLS
12178
12506
  };
12179
12507
  }
12180
12508
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -12183,13 +12511,13 @@ function createCloudflareResolveTarget(config, graph) {
12183
12511
  return {
12184
12512
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
12185
12513
  serviceName: fileNode.service,
12186
- edgeType: import_types49.EdgeType.CALLS
12514
+ edgeType: import_types51.EdgeType.CALLS
12187
12515
  };
12188
12516
  }
12189
12517
  return {
12190
- targetNodeId: (0, import_types49.infraId)("cloudflare-worker", scriptName),
12518
+ targetNodeId: (0, import_types51.infraId)("cloudflare-worker", scriptName),
12191
12519
  serviceName: scriptName,
12192
- edgeType: import_types49.EdgeType.CALLS,
12520
+ edgeType: import_types51.EdgeType.CALLS,
12193
12521
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
12194
12522
  };
12195
12523
  };
@@ -12367,12 +12695,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
12367
12695
  if (!prior || calls <= prior.calls) continue;
12368
12696
  const table = tableFromSqlStatement(row.query);
12369
12697
  if (!table) continue;
12698
+ const columns = columnsFromSqlStatement(row.query);
12370
12699
  signals.push({
12371
12700
  targetKind: NEON_SQL_TABLE_TARGET_KIND,
12372
12701
  targetName: table,
12373
12702
  callCount: calls - prior.calls,
12374
12703
  errorCount: 0,
12375
- lastObservedIso: observedAtIso
12704
+ lastObservedIso: observedAtIso,
12705
+ ...columns.length > 0 ? { columns } : {}
12376
12706
  });
12377
12707
  }
12378
12708
  for (const queryid of previous.keys()) {
@@ -12383,14 +12713,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
12383
12713
 
12384
12714
  // src/connectors/neon/resolve.ts
12385
12715
  init_cjs_shims();
12386
- var import_types53 = require("@neat.is/types");
12716
+ var import_types55 = require("@neat.is/types");
12387
12717
  function createNeonResolveTarget(config) {
12388
12718
  return (signal) => {
12389
12719
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
12390
12720
  return {
12391
- targetNodeId: (0, import_types53.infraId)("sql-table", signal.targetName),
12721
+ targetNodeId: (0, import_types55.infraId)("sql-table", signal.targetName),
12392
12722
  serviceName: config.serviceName,
12393
- edgeType: import_types53.EdgeType.CALLS,
12723
+ edgeType: import_types55.EdgeType.CALLS,
12394
12724
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
12395
12725
  };
12396
12726
  };
@@ -12954,11 +13284,11 @@ function registerRoutes(scope, ctx) {
12954
13284
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
12955
13285
  const parsed = [];
12956
13286
  for (const c of candidates) {
12957
- const r = import_types56.DivergenceTypeSchema.safeParse(c);
13287
+ const r = import_types58.DivergenceTypeSchema.safeParse(c);
12958
13288
  if (!r.success) {
12959
13289
  return reply.code(400).send({
12960
13290
  error: `unknown divergence type "${c}"`,
12961
- allowed: import_types56.DivergenceTypeSchema.options
13291
+ allowed: import_types58.DivergenceTypeSchema.options
12962
13292
  });
12963
13293
  }
12964
13294
  parsed.push(r.data);
@@ -13267,7 +13597,7 @@ function registerRoutes(scope, ctx) {
13267
13597
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
13268
13598
  let violations = await log.readAll();
13269
13599
  if (req2.query.severity) {
13270
- const sev = import_types56.PolicySeveritySchema.safeParse(req2.query.severity);
13600
+ const sev = import_types58.PolicySeveritySchema.safeParse(req2.query.severity);
13271
13601
  if (!sev.success) {
13272
13602
  return reply.code(400).send({
13273
13603
  error: "invalid severity",
@@ -13306,7 +13636,7 @@ function registerRoutes(scope, ctx) {
13306
13636
  scope.post("/policies/check", async (req2, reply) => {
13307
13637
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
13308
13638
  if (!proj) return;
13309
- const parsed = import_types56.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
13639
+ const parsed = import_types58.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
13310
13640
  if (!parsed.success) {
13311
13641
  return reply.code(400).send({
13312
13642
  error: "invalid /policies/check body",
@@ -13628,7 +13958,7 @@ init_auth();
13628
13958
  // src/unrouted.ts
13629
13959
  init_cjs_shims();
13630
13960
  var import_node_fs31 = require("fs");
13631
- var import_node_path56 = __toESM(require("path"), 1);
13961
+ var import_node_path57 = __toESM(require("path"), 1);
13632
13962
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
13633
13963
  return {
13634
13964
  timestamp: now.toISOString(),
@@ -13638,34 +13968,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
13638
13968
  };
13639
13969
  }
13640
13970
  async function appendUnroutedSpan(neatHome4, record) {
13641
- const target = import_node_path56.default.join(neatHome4, "errors.ndjson");
13971
+ const target = import_node_path57.default.join(neatHome4, "errors.ndjson");
13642
13972
  await import_node_fs31.promises.mkdir(neatHome4, { recursive: true });
13643
13973
  await import_node_fs31.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
13644
13974
  }
13645
13975
  function unroutedErrorsPath(neatHome4) {
13646
- return import_node_path56.default.join(neatHome4, "errors.ndjson");
13976
+ return import_node_path57.default.join(neatHome4, "errors.ndjson");
13647
13977
  }
13648
13978
 
13649
13979
  // src/daemon.ts
13650
- var import_types57 = require("@neat.is/types");
13980
+ var import_types59 = require("@neat.is/types");
13651
13981
  function daemonJsonPath(scanPath) {
13652
- return import_node_path57.default.join(scanPath, "neat-out", "daemon.json");
13982
+ return import_node_path58.default.join(scanPath, "neat-out", "daemon.json");
13653
13983
  }
13654
13984
  function daemonsDiscoveryDir(home) {
13655
13985
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
13656
- return import_node_path57.default.join(base, "daemons");
13986
+ return import_node_path58.default.join(base, "daemons");
13657
13987
  }
13658
13988
  function daemonDiscoveryPath(project, home) {
13659
- return import_node_path57.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
13989
+ return import_node_path58.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
13660
13990
  }
13661
13991
  function sanitizeDiscoveryName(project) {
13662
13992
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
13663
13993
  }
13664
13994
  function neatHomeFromEnv() {
13665
13995
  const env = process.env.NEAT_HOME;
13666
- if (env && env.length > 0) return import_node_path57.default.resolve(env);
13996
+ if (env && env.length > 0) return import_node_path58.default.resolve(env);
13667
13997
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
13668
- return import_node_path57.default.join(home, ".neat");
13998
+ return import_node_path58.default.join(home, ".neat");
13669
13999
  }
13670
14000
  function resolveNeatVersion() {
13671
14001
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -13734,11 +14064,11 @@ function teardownSlot(slot) {
13734
14064
  }
13735
14065
  }
13736
14066
  function neatHomeFor(opts) {
13737
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path57.default.resolve(opts.neatHome);
14067
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path58.default.resolve(opts.neatHome);
13738
14068
  const env = process.env.NEAT_HOME;
13739
- if (env && env.length > 0) return import_node_path57.default.resolve(env);
14069
+ if (env && env.length > 0) return import_node_path58.default.resolve(env);
13740
14070
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
13741
- return import_node_path57.default.join(home, ".neat");
14071
+ return import_node_path58.default.join(home, ".neat");
13742
14072
  }
13743
14073
  function routeSpanToProject(serviceName, projects) {
13744
14074
  if (!serviceName) return DEFAULT_PROJECT;
@@ -13786,11 +14116,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
13786
14116
  if (!serviceName) return true;
13787
14117
  if (serviceNameMatchesProject(serviceName, project)) return true;
13788
14118
  return graph.someNode(
13789
- (_id, attrs) => attrs.type === import_types57.NodeType.ServiceNode && attrs.name === serviceName
14119
+ (_id, attrs) => attrs.type === import_types59.NodeType.ServiceNode && attrs.name === serviceName
13790
14120
  );
13791
14121
  }
13792
14122
  async function bootstrapProject(entry2, connectors = [], neatHome4) {
13793
- const paths = pathsForProject(entry2.name, import_node_path57.default.join(entry2.path, "neat-out"));
14123
+ const paths = pathsForProject(entry2.name, import_node_path58.default.join(entry2.path, "neat-out"));
13794
14124
  try {
13795
14125
  const stat = await import_node_fs32.promises.stat(entry2.path);
13796
14126
  if (!stat.isDirectory()) {
@@ -13906,7 +14236,7 @@ async function startDaemon(opts = {}) {
13906
14236
  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;
13907
14237
  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;
13908
14238
  const singleProject = projectArg;
13909
- const singleProjectPath = singleProject && projectPathArg ? import_node_path57.default.resolve(projectPathArg) : null;
14239
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path58.default.resolve(projectPathArg) : null;
13910
14240
  if (singleProject && !singleProjectPath) {
13911
14241
  throw new Error(
13912
14242
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -13921,7 +14251,7 @@ async function startDaemon(opts = {}) {
13921
14251
  );
13922
14252
  }
13923
14253
  }
13924
- const pidPath = import_node_path57.default.join(home, "neatd.pid");
14254
+ const pidPath = import_node_path58.default.join(home, "neatd.pid");
13925
14255
  await writeAtomically(pidPath, `${process.pid}
13926
14256
  `);
13927
14257
  const slots = /* @__PURE__ */ new Map();
@@ -14333,8 +14663,8 @@ async function startDaemon(opts = {}) {
14333
14663
  let registryWatcher = null;
14334
14664
  let reloadTimer = null;
14335
14665
  if (!singleProject) try {
14336
- const regDir = import_node_path57.default.dirname(regPath);
14337
- const regBase = import_node_path57.default.basename(regPath);
14666
+ const regDir = import_node_path58.default.dirname(regPath);
14667
+ const regBase = import_node_path58.default.basename(regPath);
14338
14668
  registryWatcher = (0, import_node_fs32.watch)(regDir, (_eventType, filename) => {
14339
14669
  if (filename !== null && filename !== regBase) return;
14340
14670
  if (reloadTimer) clearTimeout(reloadTimer);
@@ -14409,7 +14739,7 @@ init_cjs_shims();
14409
14739
  var import_node_child_process2 = require("child_process");
14410
14740
  var import_node_fs33 = require("fs");
14411
14741
  var import_node_net = __toESM(require("net"), 1);
14412
- var import_node_path58 = __toESM(require("path"), 1);
14742
+ var import_node_path59 = __toESM(require("path"), 1);
14413
14743
  var DEFAULT_WEB_PORT = 6328;
14414
14744
  var DEFAULT_REST_PORT = 8080;
14415
14745
  function asValidPort(value) {
@@ -14418,11 +14748,11 @@ function asValidPort(value) {
14418
14748
  }
14419
14749
  function projectRoot() {
14420
14750
  const fromEnv = process.env.NEAT_SCAN_PATH;
14421
- return import_node_path58.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
14751
+ return import_node_path59.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
14422
14752
  }
14423
14753
  async function readDaemonPorts(root) {
14424
14754
  try {
14425
- const raw = await import_node_fs33.promises.readFile(import_node_path58.default.join(root, "neat-out", "daemon.json"), "utf8");
14755
+ const raw = await import_node_fs33.promises.readFile(import_node_path59.default.join(root, "neat-out", "daemon.json"), "utf8");
14426
14756
  const parsed = JSON.parse(raw);
14427
14757
  const ports = parsed?.ports ?? {};
14428
14758
  return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
@@ -14467,10 +14797,10 @@ function resolveWebPackageDir() {
14467
14797
  eval("require")
14468
14798
  );
14469
14799
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
14470
- return import_node_path58.default.dirname(pkgJsonPath);
14800
+ return import_node_path59.default.dirname(pkgJsonPath);
14471
14801
  }
14472
14802
  function resolveStandaloneServerEntry(webDir) {
14473
- return import_node_path58.default.join(webDir, ".next/standalone/packages/web/server.js");
14803
+ return import_node_path59.default.join(webDir, ".next/standalone/packages/web/server.js");
14474
14804
  }
14475
14805
  async function pickInternalPort() {
14476
14806
  return new Promise((resolve, reject) => {
@@ -14523,7 +14853,7 @@ async function spawnWebUI(restPort, opts = {}) {
14523
14853
  NEAT_API_URL: apiUrl
14524
14854
  };
14525
14855
  child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
14526
- cwd: import_node_path58.default.dirname(serverEntry),
14856
+ cwd: import_node_path59.default.dirname(serverEntry),
14527
14857
  env,
14528
14858
  stdio: ["ignore", "inherit", "inherit"],
14529
14859
  detached: false
@@ -14699,14 +15029,14 @@ function localVersion() {
14699
15029
  }
14700
15030
  function neatHome3() {
14701
15031
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
14702
- return import_node_path59.default.resolve(process.env.NEAT_HOME);
15032
+ return import_node_path60.default.resolve(process.env.NEAT_HOME);
14703
15033
  }
14704
15034
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
14705
- return import_node_path59.default.join(home, ".neat");
15035
+ return import_node_path60.default.join(home, ".neat");
14706
15036
  }
14707
15037
  async function readPid() {
14708
15038
  try {
14709
- const raw = await import_node_fs34.promises.readFile(import_node_path59.default.join(neatHome3(), "neatd.pid"), "utf8");
15039
+ const raw = await import_node_fs34.promises.readFile(import_node_path60.default.join(neatHome3(), "neatd.pid"), "utf8");
14710
15040
  const n = Number.parseInt(raw.trim(), 10);
14711
15041
  return Number.isFinite(n) ? n : null;
14712
15042
  } catch {