@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/index.cjs CHANGED
@@ -60,8 +60,8 @@ function mountBearerAuth(app, opts) {
60
60
  ]);
61
61
  const publicRead = opts.publicRead === true;
62
62
  app.addHook("preHandler", (req, reply, done) => {
63
- const path58 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
64
- if (exactUnauthPaths.has(path58) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path58)) {
63
+ const path59 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
64
+ if (exactUnauthPaths.has(path59) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path59)) {
65
65
  done();
66
66
  return;
67
67
  }
@@ -193,8 +193,8 @@ function reshapeGrpcRequest(req) {
193
193
  };
194
194
  }
195
195
  function resolveProtoRoot() {
196
- const here = import_node_path37.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
197
- return import_node_path37.default.resolve(here, "..", "proto");
196
+ const here = import_node_path38.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
197
+ return import_node_path38.default.resolve(here, "..", "proto");
198
198
  }
199
199
  function loadTraceService() {
200
200
  const protoRoot = resolveProtoRoot();
@@ -262,13 +262,13 @@ async function startOtelGrpcReceiver(opts) {
262
262
  })
263
263
  };
264
264
  }
265
- var import_node_url, import_node_path37, import_node_crypto2, grpc, protoLoader;
265
+ var import_node_url, import_node_path38, import_node_crypto2, grpc, protoLoader;
266
266
  var init_otel_grpc = __esm({
267
267
  "src/otel-grpc.ts"() {
268
268
  "use strict";
269
269
  init_cjs_shims();
270
270
  import_node_url = require("url");
271
- import_node_path37 = __toESM(require("path"), 1);
271
+ import_node_path38 = __toESM(require("path"), 1);
272
272
  import_node_crypto2 = require("crypto");
273
273
  grpc = __toESM(require("@grpc/grpc-js"), 1);
274
274
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -354,6 +354,46 @@ function tableFromSqlStatement(sql) {
354
354
  const m = /\b(?:from|into|update)\s+(?:"?[\w$]+"?\s*\.\s*)?"?([a-zA-Z_][\w$]*)"?/i.exec(sql);
355
355
  return m ? m[1] : null;
356
356
  }
357
+ function columnsFromSqlStatement(sql) {
358
+ if (typeof sql !== "string" || sql.length === 0) return [];
359
+ const s = sql.replace(/\s+/g, " ").trim();
360
+ if (/\bjoin\b/i.test(s)) return [];
361
+ if ((s.match(/\bfrom\b/gi) ?? []).length > 1) return [];
362
+ const bare = (raw) => {
363
+ let t = raw.trim().replace(/"/g, "");
364
+ if (/[()*]/.test(t)) return null;
365
+ t = t.split(/\s+as\s+/i)[0].trim();
366
+ t = t.split(".").pop();
367
+ return /^[a-z_][\w$]*$/i.test(t) ? t.toLowerCase() : null;
368
+ };
369
+ const isCol = (c) => c !== null;
370
+ const cols = (list) => [...new Set(list.split(",").map(bare).filter(isCol))];
371
+ const whereCols = (w) => w ? [
372
+ ...new Set(
373
+ [
374
+ ...w.matchAll(
375
+ /(?:"?[\w$]+"?\.)?"?([a-z_][\w$]*)"?\s*(?:=|<|>|<=|>=|<>|!=|\bis\b|\bin\b|\blike\b)/gi
376
+ )
377
+ ].map((match) => match[1].toLowerCase()).filter((c) => !/^(and|or|not|null)$/i.test(c))
378
+ )
379
+ ] : [];
380
+ let m;
381
+ if (m = /\binsert\s+into\s+(?:"?[\w$]+"?\.)?"?[\w$]+"?\s*\(([^)]*)\)/i.exec(s)) {
382
+ return cols(m[1]);
383
+ }
384
+ if (m = /\bupdate\s+(?:"?[\w$]+"?\.)?"?[\w$]+"?\s+set\s+(.+?)(?:\bwhere\b(.+))?$/i.exec(s)) {
385
+ const set = m[1].split(",").map((p) => bare(p.split("=")[0])).filter(isCol);
386
+ return [.../* @__PURE__ */ new Set([...set, ...whereCols(m[2] ?? void 0)])];
387
+ }
388
+ if (m = /\bdelete\s+from\s+(?:"?[\w$]+"?\.)?"?[\w$]+"?(?:\s+where\b(.+))?$/i.exec(s)) {
389
+ return whereCols(m[1] ?? void 0);
390
+ }
391
+ if (m = /\bselect\s+(.+?)\s+from\s+(?:"?[\w$]+"?\.)?"?[\w$]+"?(?:\s+where\b(.+))?$/i.exec(s)) {
392
+ if (m[1].trim() === "*") return [];
393
+ return [.../* @__PURE__ */ new Set([...cols(m[1]), ...whereCols(m[2] ?? void 0)])];
394
+ }
395
+ return [];
396
+ }
357
397
  function messagingDestinationOf(attrs) {
358
398
  for (const key of ["messaging.destination.name", "messaging.destination"]) {
359
399
  const v = attrs[key];
@@ -374,8 +414,8 @@ function websocketChannelPathOf(attrs) {
374
414
  const v = attrs[key];
375
415
  if (typeof v === "string" && v.length > 0) {
376
416
  const q = v.indexOf("?");
377
- const path58 = q === -1 ? v : v.slice(0, q);
378
- if (path58.length > 0) return path58;
417
+ const path59 = q === -1 ? v : v.slice(0, q);
418
+ if (path59.length > 0) return path59;
379
419
  }
380
420
  }
381
421
  return void 0;
@@ -412,6 +452,7 @@ function parseOtlpRequest(body) {
412
452
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
413
453
  dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
414
454
  dbTable: typeof attrs["db.statement"] === "string" ? tableFromSqlStatement(attrs["db.statement"]) ?? void 0 : void 0,
455
+ dbColumns: typeof attrs["db.statement"] === "string" ? columnsFromSqlStatement(attrs["db.statement"]) : void 0,
415
456
  httpRoute: typeof attrs["http.route"] === "string" ? attrs["http.route"] : void 0,
416
457
  httpMethod: typeof attrs["http.request.method"] === "string" ? attrs["http.request.method"] : typeof attrs["http.method"] === "string" ? attrs["http.method"] : void 0,
417
458
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
@@ -433,10 +474,10 @@ function parseOtlpRequest(body) {
433
474
  return out;
434
475
  }
435
476
  function loadProtoRoot() {
436
- const here = import_node_path38.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
437
- const protoRoot = import_node_path38.default.resolve(here, "..", "proto");
477
+ const here = import_node_path39.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
478
+ const protoRoot = import_node_path39.default.resolve(here, "..", "proto");
438
479
  const root = new import_protobufjs.default.Root();
439
- root.resolvePath = (_origin, target) => import_node_path38.default.resolve(protoRoot, target);
480
+ root.resolvePath = (_origin, target) => import_node_path39.default.resolve(protoRoot, target);
440
481
  root.loadSync(
441
482
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
442
483
  { keepCase: true }
@@ -675,12 +716,12 @@ function logSpanHandler(span) {
675
716
  `otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
676
717
  );
677
718
  }
678
- 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;
719
+ 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;
679
720
  var init_otel = __esm({
680
721
  "src/otel.ts"() {
681
722
  "use strict";
682
723
  init_cjs_shims();
683
- import_node_path38 = __toESM(require("path"), 1);
724
+ import_node_path39 = __toESM(require("path"), 1);
684
725
  import_node_url2 = require("url");
685
726
  import_fastify = __toESM(require("fastify"), 1);
686
727
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -1273,19 +1314,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1273
1314
  function longestIncomingWalk(graph, start, maxDepth) {
1274
1315
  let best = { path: [start], edges: [] };
1275
1316
  const visited = /* @__PURE__ */ new Set([start]);
1276
- function step(node, path58, edges) {
1277
- if (path58.length > best.path.length) {
1278
- best = { path: [...path58], edges: [...edges] };
1317
+ function step(node, path59, edges) {
1318
+ if (path59.length > best.path.length) {
1319
+ best = { path: [...path59], edges: [...edges] };
1279
1320
  }
1280
- if (path58.length - 1 >= maxDepth) return;
1321
+ if (path59.length - 1 >= maxDepth) return;
1281
1322
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1282
1323
  for (const [srcId, edge] of incoming) {
1283
1324
  if (visited.has(srcId)) continue;
1284
1325
  visited.add(srcId);
1285
- path58.push(srcId);
1326
+ path59.push(srcId);
1286
1327
  edges.push(edge);
1287
- step(srcId, path58, edges);
1288
- path58.pop();
1328
+ step(srcId, path59, edges);
1329
+ path59.pop();
1289
1330
  edges.pop();
1290
1331
  visited.delete(srcId);
1291
1332
  }
@@ -1492,26 +1533,26 @@ function dominantFailingCall(graph, serviceId7, visited) {
1492
1533
  return best;
1493
1534
  }
1494
1535
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1495
- const path58 = [originServiceId];
1536
+ const path59 = [originServiceId];
1496
1537
  const edges = [];
1497
1538
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1498
1539
  let current = originServiceId;
1499
1540
  for (let depth = 0; depth < maxDepth; depth++) {
1500
1541
  const hop = dominantFailingCall(graph, current, visited);
1501
1542
  if (!hop) break;
1502
- path58.push(hop.nextService);
1543
+ path59.push(hop.nextService);
1503
1544
  edges.push(hop.edge);
1504
1545
  visited.add(hop.nextService);
1505
1546
  current = hop.nextService;
1506
1547
  }
1507
1548
  if (edges.length === 0) return null;
1508
- return { path: path58, edges, culprit: current };
1549
+ return { path: path59, edges, culprit: current };
1509
1550
  }
1510
1551
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1511
1552
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1512
1553
  if (!chain) return null;
1513
1554
  const culprit = chain.culprit;
1514
- const path58 = [...chain.path];
1555
+ const path59 = [...chain.path];
1515
1556
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1516
1557
  const baseConfidence = confidenceFromMix(chain.edges);
1517
1558
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1519,14 +1560,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1519
1560
  if (loc) {
1520
1561
  let rootCauseNode = culprit;
1521
1562
  if (loc.fileNode) {
1522
- path58.push(loc.fileNode);
1563
+ path59.push(loc.fileNode);
1523
1564
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1524
1565
  rootCauseNode = loc.fileNode;
1525
1566
  }
1526
1567
  return import_types.RootCauseResultSchema.parse({
1527
1568
  rootCauseNode,
1528
1569
  rootCauseReason: loc.rootCauseReason,
1529
- traversalPath: path58,
1570
+ traversalPath: path59,
1530
1571
  edgeProvenances,
1531
1572
  confidence,
1532
1573
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1538,7 +1579,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1538
1579
  return import_types.RootCauseResultSchema.parse({
1539
1580
  rootCauseNode: culprit,
1540
1581
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1541
- traversalPath: path58,
1582
+ traversalPath: path59,
1542
1583
  edgeProvenances,
1543
1584
  confidence,
1544
1585
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2126,7 +2167,7 @@ var PolicyViolationsLog = class {
2126
2167
  };
2127
2168
 
2128
2169
  // src/ingest.ts
2129
- var import_types6 = require("@neat.is/types");
2170
+ var import_types7 = require("@neat.is/types");
2130
2171
 
2131
2172
  // src/extract/routes.ts
2132
2173
  init_cjs_shims();
@@ -3172,6 +3213,42 @@ async function addRoutes(graph, services) {
3172
3213
  return { nodesAdded, edgesAdded };
3173
3214
  }
3174
3215
 
3216
+ // src/columns.ts
3217
+ init_cjs_shims();
3218
+ var import_types6 = require("@neat.is/types");
3219
+ var OBSERVED_COLUMN_CONFIDENCE = 0.9;
3220
+ function normalizeProvenances(provenances) {
3221
+ return [...new Set(provenances)].sort();
3222
+ }
3223
+ function foldColumns(existing, names, provenance, confidence) {
3224
+ const out = (existing ?? []).map((c) => ({
3225
+ ...c,
3226
+ provenances: [...c.provenances]
3227
+ }));
3228
+ const byName = new Map(out.map((c) => [c.name, c]));
3229
+ for (const raw of names) {
3230
+ const name = raw.toLowerCase();
3231
+ const prior = byName.get(name);
3232
+ if (!prior) {
3233
+ const col = { name, provenances: [provenance], confidence };
3234
+ byName.set(name, col);
3235
+ out.push(col);
3236
+ continue;
3237
+ }
3238
+ if (!prior.provenances.includes(provenance)) {
3239
+ prior.provenances = normalizeProvenances([...prior.provenances, provenance]);
3240
+ }
3241
+ if (confidence > prior.confidence) prior.confidence = confidence;
3242
+ }
3243
+ return out;
3244
+ }
3245
+ function columnIsDeclared(col) {
3246
+ return col.provenances.includes(import_types6.Provenance.EXTRACTED);
3247
+ }
3248
+ function columnIsObserved(col) {
3249
+ return col.provenances.includes(import_types6.Provenance.OBSERVED);
3250
+ }
3251
+
3175
3252
  // src/ingest.ts
3176
3253
  var HOUR_MS = 60 * 60 * 1e3;
3177
3254
  var DAY_MS = 24 * HOUR_MS;
@@ -3471,11 +3548,11 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
3471
3548
  };
3472
3549
  }
3473
3550
  function reconcileObservedRelPath(graph, serviceName, relPath) {
3474
- if (graph.hasNode((0, import_types6.fileId)(serviceName, relPath))) return relPath;
3551
+ if (graph.hasNode((0, import_types7.fileId)(serviceName, relPath))) return relPath;
3475
3552
  let best = null;
3476
3553
  graph.forEachNode((_id, attrs) => {
3477
3554
  const a = attrs;
3478
- if (a.type !== import_types6.NodeType.FileNode || a.service !== serviceName) return;
3555
+ if (a.type !== import_types7.NodeType.FileNode || a.service !== serviceName) return;
3479
3556
  if (a.discoveredVia === "otel") return;
3480
3557
  const p = a.path;
3481
3558
  if (!p) return;
@@ -3487,14 +3564,14 @@ function reconcileObservedRelPath(graph, serviceName, relPath) {
3487
3564
  }
3488
3565
  function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
3489
3566
  const svcAttrs = graph.hasNode(serviceNodeId) ? graph.getNodeAttributes(serviceNodeId) : void 0;
3490
- const canonicalService = svcAttrs && svcAttrs.type === import_types6.NodeType.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
3567
+ const canonicalService = svcAttrs && svcAttrs.type === import_types7.NodeType.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
3491
3568
  const relPath = reconcileObservedRelPath(graph, canonicalService, callSite.relPath);
3492
- const fileNodeId = (0, import_types6.fileId)(canonicalService, relPath);
3569
+ const fileNodeId = (0, import_types7.fileId)(canonicalService, relPath);
3493
3570
  if (!graph.hasNode(fileNodeId)) {
3494
3571
  const language = languageForExt(relPath);
3495
3572
  const node = {
3496
3573
  id: fileNodeId,
3497
- type: import_types6.NodeType.FileNode,
3574
+ type: import_types7.NodeType.FileNode,
3498
3575
  service: canonicalService,
3499
3576
  path: relPath,
3500
3577
  ...language ? { language } : {},
@@ -3503,14 +3580,14 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
3503
3580
  };
3504
3581
  graph.addNode(fileNodeId, node);
3505
3582
  }
3506
- const containsId = makeObservedEdgeId(import_types6.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
3583
+ const containsId = makeObservedEdgeId(import_types7.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
3507
3584
  if (!graph.hasEdge(containsId)) {
3508
3585
  const edge = {
3509
3586
  id: containsId,
3510
3587
  source: serviceNodeId,
3511
3588
  target: fileNodeId,
3512
- type: import_types6.EdgeType.CONTAINS,
3513
- provenance: import_types6.Provenance.OBSERVED
3589
+ type: import_types7.EdgeType.CONTAINS,
3590
+ provenance: import_types7.Provenance.OBSERVED
3514
3591
  };
3515
3592
  graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
3516
3593
  }
@@ -3534,11 +3611,11 @@ function pickContainingSymbol(candidates, fn) {
3534
3611
  return [...candidates].sort(bySpan)[0].id;
3535
3612
  }
3536
3613
  function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line) {
3537
- const sid = (0, import_types6.symbolId)(service, relPath, fn);
3614
+ const sid = (0, import_types7.symbolId)(service, relPath, fn);
3538
3615
  if (!graph.hasNode(sid)) {
3539
3616
  const node = {
3540
3617
  id: sid,
3541
- type: import_types6.NodeType.SymbolNode,
3618
+ type: import_types7.NodeType.SymbolNode,
3542
3619
  kind: "function",
3543
3620
  qualname: fn,
3544
3621
  span: { startLine: line, endLine: line },
@@ -3548,14 +3625,14 @@ function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line)
3548
3625
  };
3549
3626
  graph.addNode(sid, node);
3550
3627
  }
3551
- const containsId = makeObservedEdgeId(import_types6.EdgeType.CONTAINS, fileNodeId, sid);
3628
+ const containsId = makeObservedEdgeId(import_types7.EdgeType.CONTAINS, fileNodeId, sid);
3552
3629
  if (!graph.hasEdge(containsId)) {
3553
3630
  const edge = {
3554
3631
  id: containsId,
3555
3632
  source: fileNodeId,
3556
3633
  target: sid,
3557
- type: import_types6.EdgeType.CONTAINS,
3558
- provenance: import_types6.Provenance.OBSERVED
3634
+ type: import_types7.EdgeType.CONTAINS,
3635
+ provenance: import_types7.Provenance.OBSERVED
3559
3636
  };
3560
3637
  graph.addEdgeWithKey(containsId, fileNodeId, sid, edge);
3561
3638
  }
@@ -3567,9 +3644,9 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
3567
3644
  let sawSymbol = false;
3568
3645
  const candidates = [];
3569
3646
  graph.forEachOutboundEdge(fileNodeId, (_edge, edgeAttrs, _source, target) => {
3570
- if (edgeAttrs.type !== import_types6.EdgeType.CONTAINS) return;
3647
+ if (edgeAttrs.type !== import_types7.EdgeType.CONTAINS) return;
3571
3648
  const t = graph.getNodeAttributes(target);
3572
- if (t.type !== import_types6.NodeType.SymbolNode) return;
3649
+ if (t.type !== import_types7.NodeType.SymbolNode) return;
3573
3650
  sawSymbol = true;
3574
3651
  if (line >= t.span.startLine && line <= t.span.endLine) {
3575
3652
  candidates.push({ id: target, symbol: t });
@@ -3582,17 +3659,17 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
3582
3659
  return fileNodeId;
3583
3660
  }
3584
3661
  function makeObservedEdgeId(type, source, target) {
3585
- return (0, import_types6.observedEdgeId)(source, target, type);
3662
+ return (0, import_types7.observedEdgeId)(source, target, type);
3586
3663
  }
3587
3664
  function makeInferredEdgeId(type, source, target) {
3588
- return (0, import_types6.inferredEdgeId)(source, target, type);
3665
+ return (0, import_types7.inferredEdgeId)(source, target, type);
3589
3666
  }
3590
3667
  var INFERRED_CONFIDENCE = 0.6;
3591
3668
  var STITCH_MAX_DEPTH = 2;
3592
3669
  var STITCH_EDGE_TYPES = /* @__PURE__ */ new Set([
3593
- import_types6.EdgeType.CALLS,
3594
- import_types6.EdgeType.CONNECTS_TO,
3595
- import_types6.EdgeType.DEPENDS_ON
3670
+ import_types7.EdgeType.CALLS,
3671
+ import_types7.EdgeType.CONNECTS_TO,
3672
+ import_types7.EdgeType.DEPENDS_ON
3596
3673
  ]);
3597
3674
  var WIRE_SPAN_KIND_CLIENT = 3;
3598
3675
  var WIRE_SPAN_KIND_PRODUCER = 4;
@@ -3608,11 +3685,11 @@ function spanServesGraphqlOperation(kind) {
3608
3685
  return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
3609
3686
  }
3610
3687
  function ensureGraphqlOperationNode(graph, serviceName, operationType, operationName) {
3611
- const id = (0, import_types6.graphqlOperationId)(serviceName, operationType, operationName);
3688
+ const id = (0, import_types7.graphqlOperationId)(serviceName, operationType, operationName);
3612
3689
  if (graph.hasNode(id)) return id;
3613
3690
  const node = {
3614
3691
  id,
3615
- type: import_types6.NodeType.GraphQLOperationNode,
3692
+ type: import_types7.NodeType.GraphQLOperationNode,
3616
3693
  name: operationName,
3617
3694
  service: serviceName,
3618
3695
  operationType: operationType.toLowerCase(),
@@ -3626,11 +3703,11 @@ function spanServesGrpcMethod(kind) {
3626
3703
  return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
3627
3704
  }
3628
3705
  function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
3629
- const id = (0, import_types6.grpcMethodId)(rpcService, rpcMethod);
3706
+ const id = (0, import_types7.grpcMethodId)(rpcService, rpcMethod);
3630
3707
  if (graph.hasNode(id)) return id;
3631
3708
  const node = {
3632
3709
  id,
3633
- type: import_types6.NodeType.GrpcMethodNode,
3710
+ type: import_types7.NodeType.GrpcMethodNode,
3634
3711
  name: `${rpcService}/${rpcMethod}`,
3635
3712
  rpcService,
3636
3713
  rpcMethod,
@@ -3643,11 +3720,11 @@ function spanServesWebsocketChannel(kind) {
3643
3720
  return kind !== WIRE_SPAN_KIND_CLIENT && kind !== WIRE_SPAN_KIND_PRODUCER && kind !== WIRE_SPAN_KIND_CONSUMER;
3644
3721
  }
3645
3722
  function ensureWebsocketChannelNode(graph, serviceName, channel) {
3646
- const id = (0, import_types6.websocketChannelId)(serviceName, channel);
3723
+ const id = (0, import_types7.websocketChannelId)(serviceName, channel);
3647
3724
  if (graph.hasNode(id)) return id;
3648
3725
  const node = {
3649
3726
  id,
3650
- type: import_types6.NodeType.WebSocketChannelNode,
3727
+ type: import_types7.NodeType.WebSocketChannelNode,
3651
3728
  name: channel,
3652
3729
  service: serviceName,
3653
3730
  channel,
@@ -3660,11 +3737,11 @@ function messagingDestinationKind(system) {
3660
3737
  return `${system}-topic`;
3661
3738
  }
3662
3739
  function ensureMessagingDestinationNode(graph, system, destination) {
3663
- const id = (0, import_types6.infraId)(messagingDestinationKind(system), destination);
3740
+ const id = (0, import_types7.infraId)(messagingDestinationKind(system), destination);
3664
3741
  if (graph.hasNode(id)) return id;
3665
3742
  const node = {
3666
3743
  id,
3667
- type: import_types6.NodeType.InfraNode,
3744
+ type: import_types7.NodeType.InfraNode,
3668
3745
  name: destination,
3669
3746
  provider: "self",
3670
3747
  kind: messagingDestinationKind(system)
@@ -3708,9 +3785,9 @@ function lookupParentSpan(traceId, parentSpanId, now) {
3708
3785
  };
3709
3786
  }
3710
3787
  function resolveServiceId(graph, host, env) {
3711
- const envTagged = (0, import_types6.serviceId)(host, env);
3788
+ const envTagged = (0, import_types7.serviceId)(host, env);
3712
3789
  if (graph.hasNode(envTagged)) return envTagged;
3713
- const envLess = (0, import_types6.serviceId)(host);
3790
+ const envLess = (0, import_types7.serviceId)(host);
3714
3791
  if (envLess !== envTagged && graph.hasNode(envLess)) return envLess;
3715
3792
  let sameEnv = null;
3716
3793
  let envLessMatch = null;
@@ -3718,7 +3795,7 @@ function resolveServiceId(graph, host, env) {
3718
3795
  graph.forEachNode((id, attrs) => {
3719
3796
  if (sameEnv) return;
3720
3797
  const a = attrs;
3721
- if (a.type !== import_types6.NodeType.ServiceNode) return;
3798
+ if (a.type !== import_types7.NodeType.ServiceNode) return;
3722
3799
  const matchesByName = a.name === host;
3723
3800
  const matchesByAlias = a.aliases ? a.aliases.includes(host) : false;
3724
3801
  if (!matchesByName && !matchesByAlias) return;
@@ -3733,14 +3810,14 @@ function resolveServiceId(graph, host, env) {
3733
3810
  return sameEnv ?? envLessMatch ?? anyMatch;
3734
3811
  }
3735
3812
  function frontierIdFor(host) {
3736
- return (0, import_types6.frontierId)(host);
3813
+ return (0, import_types7.frontierId)(host);
3737
3814
  }
3738
3815
  function ensureServiceNode(graph, serviceName, env) {
3739
- const id = (0, import_types6.serviceId)(serviceName, env);
3816
+ const id = (0, import_types7.serviceId)(serviceName, env);
3740
3817
  if (graph.hasNode(id)) return id;
3741
3818
  const wanted = serviceName.toLowerCase();
3742
3819
  const extractedId = graph.findNode((_nid, attrs) => {
3743
- if (attrs.type !== import_types6.NodeType.ServiceNode) return false;
3820
+ if (attrs.type !== import_types7.NodeType.ServiceNode) return false;
3744
3821
  const svc = attrs;
3745
3822
  if (svc.discoveredVia === "otel") return false;
3746
3823
  return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
@@ -3748,7 +3825,7 @@ function ensureServiceNode(graph, serviceName, env) {
3748
3825
  if (extractedId) return extractedId;
3749
3826
  const node = {
3750
3827
  id,
3751
- type: import_types6.NodeType.ServiceNode,
3828
+ type: import_types7.NodeType.ServiceNode,
3752
3829
  name: serviceName,
3753
3830
  language: "unknown",
3754
3831
  discoveredVia: "otel",
@@ -3758,11 +3835,11 @@ function ensureServiceNode(graph, serviceName, env) {
3758
3835
  return id;
3759
3836
  }
3760
3837
  function ensureInfraNode(graph, kind, name, provider) {
3761
- const id = (0, import_types6.infraId)(kind, name);
3838
+ const id = (0, import_types7.infraId)(kind, name);
3762
3839
  if (graph.hasNode(id)) return id;
3763
3840
  const node = {
3764
3841
  id,
3765
- type: import_types6.NodeType.InfraNode,
3842
+ type: import_types7.NodeType.InfraNode,
3766
3843
  name,
3767
3844
  provider,
3768
3845
  kind
@@ -3770,12 +3847,27 @@ function ensureInfraNode(graph, kind, name, provider) {
3770
3847
  graph.addNode(id, node);
3771
3848
  return id;
3772
3849
  }
3850
+ var COLUMN_BEARING_INFRA_KINDS = /* @__PURE__ */ new Set(["sql-table", "supabase-table"]);
3851
+ function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
3852
+ if (!columns || columns.length === 0 || !graph.hasNode(tableNodeId)) return;
3853
+ const node = graph.getNodeAttributes(tableNodeId);
3854
+ if (node.type !== import_types7.NodeType.InfraNode || !node.kind || !COLUMN_BEARING_INFRA_KINDS.has(node.kind)) {
3855
+ return;
3856
+ }
3857
+ graph.replaceNodeAttributes(tableNodeId, {
3858
+ ...node,
3859
+ columns: foldColumns(node.columns, columns, provenance, confidence)
3860
+ });
3861
+ }
3862
+ function mergeObservedColumns(graph, tableNodeId, columns) {
3863
+ mergeColumnsAt(graph, tableNodeId, columns, import_types7.Provenance.OBSERVED, OBSERVED_COLUMN_CONFIDENCE);
3864
+ }
3773
3865
  function ensureDatabaseNode(graph, host, engine) {
3774
- const id = (0, import_types6.databaseId)(host);
3866
+ const id = (0, import_types7.databaseId)(host);
3775
3867
  if (graph.hasNode(id)) return id;
3776
3868
  const node = {
3777
3869
  id,
3778
- type: import_types6.NodeType.DatabaseNode,
3870
+ type: import_types7.NodeType.DatabaseNode,
3779
3871
  name: host,
3780
3872
  engine,
3781
3873
  engineVersion: "unknown",
@@ -3787,11 +3879,11 @@ function ensureDatabaseNode(graph, host, engine) {
3787
3879
  return id;
3788
3880
  }
3789
3881
  function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
3790
- const id = (0, import_types6.localDatabaseId)(serviceName, name);
3882
+ const id = (0, import_types7.localDatabaseId)(serviceName, name);
3791
3883
  if (graph.hasNode(id)) return id;
3792
3884
  const node = {
3793
3885
  id,
3794
- type: import_types6.NodeType.DatabaseNode,
3886
+ type: import_types7.NodeType.DatabaseNode,
3795
3887
  name,
3796
3888
  engine,
3797
3889
  engineVersion: "unknown",
@@ -3806,17 +3898,17 @@ function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
3806
3898
  const sources = [serviceNodeId];
3807
3899
  for (const edgeId of graph.outboundEdges(serviceNodeId)) {
3808
3900
  const e = graph.getEdgeAttributes(edgeId);
3809
- if (e.type === import_types6.EdgeType.CONTAINS) sources.push(e.target);
3901
+ if (e.type === import_types7.EdgeType.CONTAINS) sources.push(e.target);
3810
3902
  }
3811
3903
  const matches = /* @__PURE__ */ new Set();
3812
3904
  for (const src of sources) {
3813
3905
  if (!graph.hasNode(src)) continue;
3814
3906
  for (const edgeId of graph.outboundEdges(src)) {
3815
3907
  const edge = graph.getEdgeAttributes(edgeId);
3816
- if (edge.type !== import_types6.EdgeType.CONNECTS_TO || edge.provenance !== import_types6.Provenance.EXTRACTED) continue;
3908
+ if (edge.type !== import_types7.EdgeType.CONNECTS_TO || edge.provenance !== import_types7.Provenance.EXTRACTED) continue;
3817
3909
  if (!graph.hasNode(edge.target)) continue;
3818
3910
  const target = graph.getNodeAttributes(edge.target);
3819
- if (target.type !== import_types6.NodeType.DatabaseNode || target.engine !== engine) continue;
3911
+ if (target.type !== import_types7.NodeType.DatabaseNode || target.engine !== engine) continue;
3820
3912
  matches.add(edge.target);
3821
3913
  }
3822
3914
  }
@@ -3831,7 +3923,7 @@ function ensureFrontierNode(graph, host, ts) {
3831
3923
  }
3832
3924
  const node = {
3833
3925
  id,
3834
- type: import_types6.NodeType.FrontierNode,
3926
+ type: import_types7.NodeType.FrontierNode,
3835
3927
  name: host,
3836
3928
  host,
3837
3929
  firstObserved: ts,
@@ -3855,11 +3947,11 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
3855
3947
  };
3856
3948
  const updated = {
3857
3949
  ...existing,
3858
- provenance: import_types6.Provenance.OBSERVED,
3950
+ provenance: import_types7.Provenance.OBSERVED,
3859
3951
  lastObserved: ts,
3860
3952
  callCount: newSpanCount,
3861
3953
  signal: newSignal,
3862
- confidence: (0, import_types6.confidenceForObservedSignal)(newSignal),
3954
+ confidence: (0, import_types7.confidenceForObservedSignal)(newSignal),
3863
3955
  grain
3864
3956
  // backfills legacy edges that predate ADR-142
3865
3957
  };
@@ -3876,8 +3968,8 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
3876
3968
  source,
3877
3969
  target,
3878
3970
  type,
3879
- provenance: import_types6.Provenance.OBSERVED,
3880
- confidence: (0, import_types6.confidenceForObservedSignal)(signal),
3971
+ provenance: import_types7.Provenance.OBSERVED,
3972
+ confidence: (0, import_types7.confidenceForObservedSignal)(signal),
3881
3973
  lastObserved: ts,
3882
3974
  callCount: 1,
3883
3975
  signal,
@@ -3899,9 +3991,9 @@ function stitchTrace(graph, sourceServiceId, ts) {
3899
3991
  const outbound = graph.outboundEdges(nodeId);
3900
3992
  for (const edgeId of outbound) {
3901
3993
  const edge = graph.getEdgeAttributes(edgeId);
3902
- if (edge.provenance !== import_types6.Provenance.EXTRACTED) continue;
3994
+ if (edge.provenance !== import_types7.Provenance.EXTRACTED) continue;
3903
3995
  if (!STITCH_EDGE_TYPES.has(edge.type)) continue;
3904
- if (graph.hasEdge((0, import_types6.observedEdgeId)(edge.source, edge.target, edge.type))) continue;
3996
+ if (graph.hasEdge((0, import_types7.observedEdgeId)(edge.source, edge.target, edge.type))) continue;
3905
3997
  upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts);
3906
3998
  if (!visited.has(edge.target)) {
3907
3999
  visited.add(edge.target);
@@ -3923,7 +4015,7 @@ function upsertInferredEdge(graph, type, source, target, ts) {
3923
4015
  source,
3924
4016
  target,
3925
4017
  type,
3926
- provenance: import_types6.Provenance.INFERRED,
4018
+ provenance: import_types7.Provenance.INFERRED,
3927
4019
  confidence: INFERRED_CONFIDENCE,
3928
4020
  lastObserved: ts
3929
4021
  };
@@ -3934,12 +4026,12 @@ async function appendErrorEvent(ctx, ev) {
3934
4026
  await import_node_fs6.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
3935
4027
  }
3936
4028
  function incidentAffectedNode(span, graph, scanPath) {
3937
- const sid = (0, import_types6.serviceId)(span.service, span.env);
4029
+ const sid = (0, import_types7.serviceId)(span.service, span.env);
3938
4030
  const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
3939
4031
  const callSite = callSiteFromSpan(span, serviceNode, scanPath);
3940
4032
  if (callSite) {
3941
4033
  const relPath = graph ? reconcileObservedRelPath(graph, span.service, callSite.relPath) : callSite.relPath;
3942
- return (0, import_types6.fileId)(span.service, relPath);
4034
+ return (0, import_types7.fileId)(span.service, relPath);
3943
4035
  }
3944
4036
  return sid;
3945
4037
  }
@@ -4064,7 +4156,7 @@ function findRouteNodeByHttpRoute(graph, serviceName, method, httpRoute) {
4064
4156
  graph.forEachNode((id, attrs) => {
4065
4157
  if (found) return;
4066
4158
  const a = attrs;
4067
- if (a.type !== import_types6.NodeType.RouteNode || a.service !== serviceName) return;
4159
+ if (a.type !== import_types7.NodeType.RouteNode || a.service !== serviceName) return;
4068
4160
  if (m && a.method !== "ALL" && a.method !== m) return;
4069
4161
  if (normalizePathTemplate(a.pathTemplate) === target) found = id;
4070
4162
  });
@@ -4095,7 +4187,7 @@ async function handleSpan(ctx, span) {
4095
4187
  let targetId;
4096
4188
  if (host) {
4097
4189
  ensureDatabaseNode(ctx.graph, host, span.dbSystem);
4098
- targetId = (0, import_types6.databaseId)(host);
4190
+ targetId = (0, import_types7.databaseId)(host);
4099
4191
  } else {
4100
4192
  const declared = findDeclaredDatabaseForService(ctx.graph, sourceId, span.dbSystem);
4101
4193
  if (declared) {
@@ -4112,7 +4204,7 @@ async function handleSpan(ctx, span) {
4112
4204
  }
4113
4205
  const result = upsertObservedEdge(
4114
4206
  ctx.graph,
4115
- import_types6.EdgeType.CONNECTS_TO,
4207
+ import_types7.EdgeType.CONNECTS_TO,
4116
4208
  observedSource(),
4117
4209
  targetId,
4118
4210
  ts,
@@ -4124,7 +4216,7 @@ async function handleSpan(ctx, span) {
4124
4216
  const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
4125
4217
  upsertObservedEdge(
4126
4218
  ctx.graph,
4127
- import_types6.EdgeType.CALLS,
4219
+ import_types7.EdgeType.CALLS,
4128
4220
  observedSource(),
4129
4221
  collectionId,
4130
4222
  ts,
@@ -4136,13 +4228,14 @@ async function handleSpan(ctx, span) {
4136
4228
  const tableId = ensureInfraNode(ctx.graph, "sql-table", span.dbTable, "self");
4137
4229
  upsertObservedEdge(
4138
4230
  ctx.graph,
4139
- import_types6.EdgeType.CALLS,
4231
+ import_types7.EdgeType.CALLS,
4140
4232
  observedSource(),
4141
4233
  tableId,
4142
4234
  ts,
4143
4235
  isError,
4144
4236
  callSiteEvidence
4145
4237
  );
4238
+ mergeObservedColumns(ctx.graph, tableId, span.dbColumns);
4146
4239
  }
4147
4240
  }
4148
4241
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
@@ -4151,7 +4244,7 @@ async function handleSpan(ctx, span) {
4151
4244
  span.messagingSystem,
4152
4245
  span.messagingDestination
4153
4246
  );
4154
- const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types6.EdgeType.CONSUMES_FROM : import_types6.EdgeType.PUBLISHES_TO;
4247
+ const edgeType = span.kind === WIRE_SPAN_KIND_CONSUMER ? import_types7.EdgeType.CONSUMES_FROM : import_types7.EdgeType.PUBLISHES_TO;
4155
4248
  const result = upsertObservedEdge(
4156
4249
  ctx.graph,
4157
4250
  edgeType,
@@ -4171,7 +4264,7 @@ async function handleSpan(ctx, span) {
4171
4264
  );
4172
4265
  const result = upsertObservedEdge(
4173
4266
  ctx.graph,
4174
- import_types6.EdgeType.CONTAINS,
4267
+ import_types7.EdgeType.CONTAINS,
4175
4268
  observedSource(),
4176
4269
  targetId,
4177
4270
  ts,
@@ -4183,7 +4276,7 @@ async function handleSpan(ctx, span) {
4183
4276
  const targetId = ensureGrpcMethodNode(ctx.graph, span.rpcService, span.rpcMethod);
4184
4277
  const result = upsertObservedEdge(
4185
4278
  ctx.graph,
4186
- import_types6.EdgeType.CONTAINS,
4279
+ import_types7.EdgeType.CONTAINS,
4187
4280
  observedSource(),
4188
4281
  targetId,
4189
4282
  ts,
@@ -4199,7 +4292,7 @@ async function handleSpan(ctx, span) {
4199
4292
  );
4200
4293
  const result = upsertObservedEdge(
4201
4294
  ctx.graph,
4202
- import_types6.EdgeType.CONNECTS_TO,
4295
+ import_types7.EdgeType.CONNECTS_TO,
4203
4296
  observedSource(),
4204
4297
  targetId,
4205
4298
  ts,
@@ -4215,7 +4308,7 @@ async function handleSpan(ctx, span) {
4215
4308
  if (targetId && targetId !== sourceId) {
4216
4309
  upsertObservedEdge(
4217
4310
  ctx.graph,
4218
- import_types6.EdgeType.CALLS,
4311
+ import_types7.EdgeType.CALLS,
4219
4312
  observedSource(),
4220
4313
  targetId,
4221
4314
  ts,
@@ -4228,7 +4321,7 @@ async function handleSpan(ctx, span) {
4228
4321
  const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
4229
4322
  upsertObservedEdge(
4230
4323
  ctx.graph,
4231
- import_types6.EdgeType.CALLS,
4324
+ import_types7.EdgeType.CALLS,
4232
4325
  observedSource(),
4233
4326
  frontierNodeId,
4234
4327
  ts,
@@ -4254,7 +4347,7 @@ async function handleSpan(ctx, span) {
4254
4347
  } : void 0;
4255
4348
  upsertObservedEdge(
4256
4349
  ctx.graph,
4257
- import_types6.EdgeType.CALLS,
4350
+ import_types7.EdgeType.CALLS,
4258
4351
  fallbackSource,
4259
4352
  sourceId,
4260
4353
  ts,
@@ -4264,7 +4357,7 @@ async function handleSpan(ctx, span) {
4264
4357
  }
4265
4358
  }
4266
4359
  }
4267
- if (span.httpRoute && (span.kind === 2 || span.kind === 0 || span.kind === void 0)) {
4360
+ if (span.httpRoute && (span.kind === 2 || span.kind === 1 || span.kind === 0 || span.kind === void 0)) {
4268
4361
  const routeNodeId = findRouteNodeByHttpRoute(
4269
4362
  ctx.graph,
4270
4363
  span.service,
@@ -4273,7 +4366,7 @@ async function handleSpan(ctx, span) {
4273
4366
  );
4274
4367
  if (routeNodeId) {
4275
4368
  const routeSvc = ctx.graph.getNodeAttributes(routeNodeId).service;
4276
- upsertObservedEdge(ctx.graph, import_types6.EdgeType.CONTAINS, (0, import_types6.serviceId)(routeSvc), routeNodeId, ts, isError);
4369
+ upsertObservedEdge(ctx.graph, import_types7.EdgeType.CONTAINS, (0, import_types7.serviceId)(routeSvc), routeNodeId, ts, isError);
4277
4370
  }
4278
4371
  }
4279
4372
  if (span.statusCode === 2) {
@@ -4312,7 +4405,7 @@ function promoteFrontierNodes(graph, opts = {}) {
4312
4405
  const aliasIndex = /* @__PURE__ */ new Map();
4313
4406
  graph.forEachNode((id, attrs) => {
4314
4407
  const a = attrs;
4315
- if (a.type !== import_types6.NodeType.ServiceNode) return;
4408
+ if (a.type !== import_types7.NodeType.ServiceNode) return;
4316
4409
  aliasIndex.set(a.name, id);
4317
4410
  if (a.aliases) {
4318
4411
  for (const alias of a.aliases) aliasIndex.set(alias, id);
@@ -4321,7 +4414,7 @@ function promoteFrontierNodes(graph, opts = {}) {
4321
4414
  const toPromote = [];
4322
4415
  graph.forEachNode((id, attrs) => {
4323
4416
  const a = attrs;
4324
- if (a.type !== import_types6.NodeType.FrontierNode) return;
4417
+ if (a.type !== import_types7.NodeType.FrontierNode) return;
4325
4418
  const target = aliasIndex.get(a.host);
4326
4419
  if (!target) return;
4327
4420
  if (target === id) return;
@@ -4355,7 +4448,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId7) {
4355
4448
  }
4356
4449
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
4357
4450
  graph.dropEdge(oldEdgeId);
4358
- 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);
4451
+ 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);
4359
4452
  if (graph.hasEdge(newId)) {
4360
4453
  const existing = graph.getEdgeAttributes(newId);
4361
4454
  const merged = {
@@ -4389,12 +4482,12 @@ async function markStaleEdges(graph, options = {}) {
4389
4482
  const project = options.project ?? DEFAULT_PROJECT;
4390
4483
  graph.forEachEdge((id, attrs) => {
4391
4484
  const e = attrs;
4392
- if (e.provenance !== import_types6.Provenance.OBSERVED) return;
4485
+ if (e.provenance !== import_types7.Provenance.OBSERVED) return;
4393
4486
  if (!e.lastObserved) return;
4394
4487
  const threshold = thresholdForEdgeType(e.type, thresholds);
4395
4488
  const age = now - new Date(e.lastObserved).getTime();
4396
4489
  if (age > threshold) {
4397
- const updated = { ...e, provenance: import_types6.Provenance.STALE, confidence: 0.3 };
4490
+ const updated = { ...e, provenance: import_types7.Provenance.STALE, confidence: 0.3 };
4398
4491
  graph.replaceEdgeAttributes(id, updated);
4399
4492
  events.push({
4400
4493
  edgeId: id,
@@ -4411,8 +4504,8 @@ async function markStaleEdges(graph, options = {}) {
4411
4504
  project,
4412
4505
  payload: {
4413
4506
  edgeId: id,
4414
- from: import_types6.Provenance.OBSERVED,
4415
- to: import_types6.Provenance.STALE
4507
+ from: import_types7.Provenance.OBSERVED,
4508
+ to: import_types7.Provenance.STALE
4416
4509
  }
4417
4510
  });
4418
4511
  }
@@ -4521,7 +4614,7 @@ function mergeSnapshot(graph, snapshot) {
4521
4614
  const validEdges = [];
4522
4615
  for (const node of incomingNodes) {
4523
4616
  if (node.attributes === void 0) continue;
4524
- const parsed = import_types6.GraphNodeSchema.safeParse(node.attributes);
4617
+ const parsed = import_types7.GraphNodeSchema.safeParse(node.attributes);
4525
4618
  if (!parsed.success) {
4526
4619
  issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
4527
4620
  continue;
@@ -4530,7 +4623,7 @@ function mergeSnapshot(graph, snapshot) {
4530
4623
  }
4531
4624
  for (const edge of incomingEdges) {
4532
4625
  if (edge.attributes === void 0) continue;
4533
- const parsed = import_types6.GraphEdgeSchema.safeParse(edge.attributes);
4626
+ const parsed = import_types7.GraphEdgeSchema.safeParse(edge.attributes);
4534
4627
  if (!parsed.success) {
4535
4628
  const label = edge.key ?? `${edge.source}->${edge.target}`;
4536
4629
  issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
@@ -4564,7 +4657,7 @@ var import_node_fs10 = require("fs");
4564
4657
  var import_node_path11 = __toESM(require("path"), 1);
4565
4658
  var import_ignore = __toESM(require("ignore"), 1);
4566
4659
  var import_minimatch2 = require("minimatch");
4567
- var import_types8 = require("@neat.is/types");
4660
+ var import_types9 = require("@neat.is/types");
4568
4661
 
4569
4662
  // src/extract/python.ts
4570
4663
  init_cjs_shims();
@@ -4637,7 +4730,7 @@ function pythonToPackage(service) {
4637
4730
  init_cjs_shims();
4638
4731
  var import_node_fs8 = require("fs");
4639
4732
  var import_node_path9 = __toESM(require("path"), 1);
4640
- var import_types7 = require("@neat.is/types");
4733
+ var import_types8 = require("@neat.is/types");
4641
4734
  function parseGoMod(source) {
4642
4735
  const module2 = source.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
4643
4736
  if (!module2) return null;
@@ -4665,8 +4758,8 @@ async function discoverGoService(scanPath, dir) {
4665
4758
  const name = mod.module.split("/").filter(Boolean).pop() ?? mod.module;
4666
4759
  const pkg = { name, dependencies: mod.dependencies };
4667
4760
  const node = {
4668
- id: (0, import_types7.serviceId)(name),
4669
- type: import_types7.NodeType.ServiceNode,
4761
+ id: (0, import_types8.serviceId)(name),
4762
+ type: import_types8.NodeType.ServiceNode,
4670
4763
  name,
4671
4764
  language: "go",
4672
4765
  dependencies: mod.dependencies,
@@ -4852,8 +4945,8 @@ async function discoverNodeService(scanPath, dir) {
4852
4945
  const framework = detectJsFramework(pkg);
4853
4946
  const language = await detectJsServiceLanguage(dir, pkg);
4854
4947
  const node = {
4855
- id: (0, import_types8.serviceId)(pkg.name),
4856
- type: import_types8.NodeType.ServiceNode,
4948
+ id: (0, import_types9.serviceId)(pkg.name),
4949
+ type: import_types9.NodeType.ServiceNode,
4857
4950
  name: pkg.name,
4858
4951
  language,
4859
4952
  version: pkg.version,
@@ -4869,8 +4962,8 @@ async function discoverPyService(scanPath, dir) {
4869
4962
  if (!py) return null;
4870
4963
  const pkg = pythonToPackage(py);
4871
4964
  const node = {
4872
- id: (0, import_types8.serviceId)(py.name),
4873
- type: import_types8.NodeType.ServiceNode,
4965
+ id: (0, import_types9.serviceId)(py.name),
4966
+ type: import_types9.NodeType.ServiceNode,
4874
4967
  name: py.name,
4875
4968
  language: "python",
4876
4969
  version: py.version,
@@ -4966,7 +5059,7 @@ init_cjs_shims();
4966
5059
  var import_node_path12 = __toESM(require("path"), 1);
4967
5060
  var import_node_fs11 = require("fs");
4968
5061
  var import_yaml2 = require("yaml");
4969
- var import_types9 = require("@neat.is/types");
5062
+ var import_types10 = require("@neat.is/types");
4970
5063
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
4971
5064
  "Service",
4972
5065
  "Deployment",
@@ -4976,7 +5069,7 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
4976
5069
  function addAliases(graph, serviceId7, candidates) {
4977
5070
  if (!graph.hasNode(serviceId7)) return;
4978
5071
  const node = graph.getNodeAttributes(serviceId7);
4979
- if (node.type !== import_types9.NodeType.ServiceNode) return;
5072
+ if (node.type !== import_types10.NodeType.ServiceNode) return;
4980
5073
  const set = new Set(node.aliases ?? []);
4981
5074
  for (const c of candidates) {
4982
5075
  if (!c) continue;
@@ -5157,7 +5250,7 @@ var import_node_path14 = __toESM(require("path"), 1);
5157
5250
  var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
5158
5251
  var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
5159
5252
  var import_tree_sitter_typescript = __toESM(require("tree-sitter-typescript"), 1);
5160
- var import_types10 = require("@neat.is/types");
5253
+ var import_types11 = require("@neat.is/types");
5161
5254
  var PARSE_CHUNK2 = 16384;
5162
5255
  var GRAMMAR_BY_EXT = {
5163
5256
  ".ts": import_tree_sitter_typescript.default.typescript,
@@ -5248,7 +5341,7 @@ function disambiguate(defs) {
5248
5341
  }
5249
5342
  async function addSymbols(graph, services) {
5250
5343
  const parsers = /* @__PURE__ */ new Map();
5251
- const parserForExt = (ext) => {
5344
+ const parserForExt2 = (ext) => {
5252
5345
  const grammar = GRAMMAR_BY_EXT[ext];
5253
5346
  if (!grammar) return null;
5254
5347
  let parser = parsers.get(ext);
@@ -5264,7 +5357,7 @@ async function addSymbols(graph, services) {
5264
5357
  for (const service of services) {
5265
5358
  const files = await loadSourceFiles(service.dir);
5266
5359
  for (const file of files) {
5267
- const parser = parserForExt(import_node_path14.default.extname(file.path));
5360
+ const parser = parserForExt2(import_node_path14.default.extname(file.path));
5268
5361
  if (!parser) continue;
5269
5362
  const relPath = toPosix(import_node_path14.default.relative(service.dir, file.path));
5270
5363
  let defs;
@@ -5285,11 +5378,11 @@ async function addSymbols(graph, services) {
5285
5378
  nodesAdded += fn;
5286
5379
  edgesAdded += fe;
5287
5380
  for (const { def, disambiguator } of disambiguate(defs)) {
5288
- const sid = (0, import_types10.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
5381
+ const sid = (0, import_types11.symbolId)(service.pkg.name, relPath, def.qualname, disambiguator);
5289
5382
  if (!graph.hasNode(sid)) {
5290
5383
  const node = {
5291
5384
  id: sid,
5292
- type: import_types10.NodeType.SymbolNode,
5385
+ type: import_types11.NodeType.SymbolNode,
5293
5386
  kind: def.kind,
5294
5387
  qualname: def.qualname,
5295
5388
  span: { startLine: def.startLine, endLine: def.endLine },
@@ -5300,15 +5393,15 @@ async function addSymbols(graph, services) {
5300
5393
  graph.addNode(sid, node);
5301
5394
  nodesAdded++;
5302
5395
  }
5303
- const containsId = (0, import_types10.extractedEdgeId)(fileNodeId, sid, import_types10.EdgeType.CONTAINS);
5396
+ const containsId = (0, import_types11.extractedEdgeId)(fileNodeId, sid, import_types11.EdgeType.CONTAINS);
5304
5397
  if (!graph.hasEdge(containsId)) {
5305
5398
  const edge = {
5306
5399
  id: containsId,
5307
5400
  source: fileNodeId,
5308
5401
  target: sid,
5309
- type: import_types10.EdgeType.CONTAINS,
5310
- provenance: import_types10.Provenance.EXTRACTED,
5311
- confidence: (0, import_types10.confidenceForExtracted)("structural"),
5402
+ type: import_types11.EdgeType.CONTAINS,
5403
+ provenance: import_types11.Provenance.EXTRACTED,
5404
+ confidence: (0, import_types11.confidenceForExtracted)("structural"),
5312
5405
  evidence: {
5313
5406
  file: relPath,
5314
5407
  line: def.startLine,
@@ -5328,7 +5421,7 @@ async function addSymbols(graph, services) {
5328
5421
  init_cjs_shims();
5329
5422
  var import_node_path16 = __toESM(require("path"), 1);
5330
5423
  var import_tree_sitter4 = __toESM(require("tree-sitter"), 1);
5331
- var import_types12 = require("@neat.is/types");
5424
+ var import_types13 = require("@neat.is/types");
5332
5425
 
5333
5426
  // src/extract/imports.ts
5334
5427
  init_cjs_shims();
@@ -5338,7 +5431,7 @@ var import_tree_sitter3 = __toESM(require("tree-sitter"), 1);
5338
5431
  var import_tree_sitter_javascript3 = __toESM(require("tree-sitter-javascript"), 1);
5339
5432
  var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
5340
5433
  var import_tree_sitter_go2 = __toESM(require("tree-sitter-go"), 1);
5341
- var import_types11 = require("@neat.is/types");
5434
+ var import_types12 = require("@neat.is/types");
5342
5435
  var PARSE_CHUNK3 = 16384;
5343
5436
  function parseSource3(parser, source) {
5344
5437
  return parser.parse(
@@ -5612,17 +5705,17 @@ async function resolveGoImport(specifier, modulePath, serviceDir) {
5612
5705
  return toPosix(import_node_path15.default.relative(serviceDir, candidates[0]));
5613
5706
  }
5614
5707
  function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
5615
- const importeeFileId = (0, import_types11.fileId)(serviceName, importeeRelPath);
5708
+ const importeeFileId = (0, import_types12.fileId)(serviceName, importeeRelPath);
5616
5709
  if (!graph.hasNode(importeeFileId)) return 0;
5617
- const edgeId = (0, import_types11.extractedEdgeId)(importerFileId, importeeFileId, import_types11.EdgeType.IMPORTS);
5710
+ const edgeId = (0, import_types12.extractedEdgeId)(importerFileId, importeeFileId, import_types12.EdgeType.IMPORTS);
5618
5711
  if (graph.hasEdge(edgeId)) return 0;
5619
5712
  const edge = {
5620
5713
  id: edgeId,
5621
5714
  source: importerFileId,
5622
5715
  target: importeeFileId,
5623
- type: import_types11.EdgeType.IMPORTS,
5624
- provenance: import_types11.Provenance.EXTRACTED,
5625
- confidence: (0, import_types11.confidenceForExtracted)("structural"),
5716
+ type: import_types12.EdgeType.IMPORTS,
5717
+ provenance: import_types12.Provenance.EXTRACTED,
5718
+ confidence: (0, import_types12.confidenceForExtracted)("structural"),
5626
5719
  evidence: { file: importerRelPath, line, snippet: snippet2 }
5627
5720
  };
5628
5721
  graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
@@ -5639,7 +5732,7 @@ async function addImports(graph, services) {
5639
5732
  for (const file of files) {
5640
5733
  if (isTestPath(file.path)) continue;
5641
5734
  const relFile = toPosix(import_node_path15.default.relative(service.dir, file.path));
5642
- const importerFileId = (0, import_types11.fileId)(service.pkg.name, relFile);
5735
+ const importerFileId = (0, import_types12.fileId)(service.pkg.name, relFile);
5643
5736
  const isPython = import_node_path15.default.extname(file.path) === ".py";
5644
5737
  const isGo = import_node_path15.default.extname(file.path) === ".go";
5645
5738
  if (isGo) {
@@ -5800,7 +5893,7 @@ function stringInner(node) {
5800
5893
  }
5801
5894
  async function addSymbolEdges(graph, services) {
5802
5895
  const parsers = /* @__PURE__ */ new Map();
5803
- const parserForExt = (ext) => {
5896
+ const parserForExt2 = (ext) => {
5804
5897
  const grammar = GRAMMAR_BY_EXT[ext];
5805
5898
  if (!grammar) return null;
5806
5899
  let parser = parsers.get(ext);
@@ -5817,7 +5910,7 @@ async function addSymbolEdges(graph, services) {
5817
5910
  const tsPaths = await loadTsPathConfig(service.dir);
5818
5911
  const files = await loadSourceFiles(service.dir);
5819
5912
  for (const file of files) {
5820
- const parser = parserForExt(import_node_path16.default.extname(file.path));
5913
+ const parser = parserForExt2(import_node_path16.default.extname(file.path));
5821
5914
  if (!parser) continue;
5822
5915
  const relPath = toPosix(import_node_path16.default.relative(service.dir, file.path));
5823
5916
  const fileDir = import_node_path16.default.dirname(file.path);
@@ -5830,7 +5923,7 @@ async function addSymbolEdges(graph, services) {
5830
5923
  }
5831
5924
  const disambiguated = disambiguate(collectSymbolDefs(root));
5832
5925
  const locals = disambiguated.map(({ def, disambiguator }) => ({
5833
- sid: (0, import_types12.symbolId)(serviceName, relPath, def.qualname, disambiguator),
5926
+ sid: (0, import_types13.symbolId)(serviceName, relPath, def.qualname, disambiguator),
5834
5927
  qualname: def.qualname,
5835
5928
  kind: def.kind,
5836
5929
  startLine: def.startLine,
@@ -5859,10 +5952,10 @@ async function addSymbolEdges(graph, services) {
5859
5952
  if (local) return local.kind === wantKind ? local.sid : null;
5860
5953
  const imp = resolvedImports.get(name);
5861
5954
  if (imp) {
5862
- const candidate = (0, import_types12.symbolId)(serviceName, imp.targetRelPath, imp.importedName);
5955
+ const candidate = (0, import_types13.symbolId)(serviceName, imp.targetRelPath, imp.importedName);
5863
5956
  if (graph.hasNode(candidate)) {
5864
5957
  const node = graph.getNodeAttributes(candidate);
5865
- if (node.type === import_types12.NodeType.SymbolNode && node.kind === wantKind) return candidate;
5958
+ if (node.type === import_types13.NodeType.SymbolNode && node.kind === wantKind) return candidate;
5866
5959
  }
5867
5960
  }
5868
5961
  return null;
@@ -5889,7 +5982,7 @@ async function addSymbolEdges(graph, services) {
5889
5982
  sourceSid: self.sid,
5890
5983
  targetName: ext.name,
5891
5984
  wantKind: "class",
5892
- edgeType: import_types12.EdgeType.INHERITS,
5985
+ edgeType: import_types13.EdgeType.INHERITS,
5893
5986
  line: ext.line
5894
5987
  });
5895
5988
  }
@@ -5898,7 +5991,7 @@ async function addSymbolEdges(graph, services) {
5898
5991
  sourceSid: self.sid,
5899
5992
  targetName: impl.name,
5900
5993
  wantKind: "class",
5901
- edgeType: import_types12.EdgeType.IMPLEMENTS,
5994
+ edgeType: import_types13.EdgeType.IMPLEMENTS,
5902
5995
  line: impl.line
5903
5996
  });
5904
5997
  }
@@ -5914,7 +6007,7 @@ async function addSymbolEdges(graph, services) {
5914
6007
  sourceSid: caller.sid,
5915
6008
  targetName: fn.text,
5916
6009
  wantKind: "function",
5917
- edgeType: import_types12.EdgeType.CALLS,
6010
+ edgeType: import_types13.EdgeType.CALLS,
5918
6011
  line
5919
6012
  });
5920
6013
  }
@@ -5930,15 +6023,15 @@ async function addSymbolEdges(graph, services) {
5930
6023
  const targetSid = resolveTarget(req.targetName, req.wantKind);
5931
6024
  if (!targetSid) continue;
5932
6025
  if (targetSid === req.sourceSid) continue;
5933
- const edgeId = (0, import_types12.extractedEdgeId)(req.sourceSid, targetSid, req.edgeType);
6026
+ const edgeId = (0, import_types13.extractedEdgeId)(req.sourceSid, targetSid, req.edgeType);
5934
6027
  if (graph.hasEdge(edgeId)) continue;
5935
6028
  const edge = {
5936
6029
  id: edgeId,
5937
6030
  source: req.sourceSid,
5938
6031
  target: targetSid,
5939
6032
  type: req.edgeType,
5940
- provenance: import_types12.Provenance.EXTRACTED,
5941
- confidence: (0, import_types12.confidenceForExtracted)("structural"),
6033
+ provenance: import_types13.Provenance.EXTRACTED,
6034
+ confidence: (0, import_types13.confidenceForExtracted)("structural"),
5942
6035
  evidence: {
5943
6036
  file: relPath,
5944
6037
  line: req.line,
@@ -5956,7 +6049,7 @@ async function addSymbolEdges(graph, services) {
5956
6049
  // src/extract/databases/index.ts
5957
6050
  init_cjs_shims();
5958
6051
  var import_node_path24 = __toESM(require("path"), 1);
5959
- var import_types13 = require("@neat.is/types");
6052
+ var import_types14 = require("@neat.is/types");
5960
6053
 
5961
6054
  // src/extract/databases/db-config-yaml.ts
5962
6055
  init_cjs_shims();
@@ -6460,8 +6553,8 @@ function compatibleDriversFor(engine) {
6460
6553
  }
6461
6554
  function toDatabaseNode(config) {
6462
6555
  return {
6463
- id: (0, import_types13.databaseId)(config.host),
6464
- type: import_types13.NodeType.DatabaseNode,
6556
+ id: (0, import_types14.databaseId)(config.host),
6557
+ type: import_types14.NodeType.DatabaseNode,
6465
6558
  name: config.database || config.host,
6466
6559
  engine: config.engine,
6467
6560
  engineVersion: config.engineVersion,
@@ -6602,12 +6695,12 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
6602
6695
  edgesAdded += fe;
6603
6696
  const evidenceFile = toPosix(import_node_path24.default.relative(scanPath, config.sourceFile));
6604
6697
  const edge = {
6605
- id: (0, import_types3.extractedEdgeId)(fileNodeId, dbNode.id, import_types13.EdgeType.CONNECTS_TO),
6698
+ id: (0, import_types3.extractedEdgeId)(fileNodeId, dbNode.id, import_types14.EdgeType.CONNECTS_TO),
6606
6699
  source: fileNodeId,
6607
6700
  target: dbNode.id,
6608
- type: import_types13.EdgeType.CONNECTS_TO,
6609
- provenance: import_types13.Provenance.EXTRACTED,
6610
- confidence: (0, import_types13.confidenceForExtracted)("structural"),
6701
+ type: import_types14.EdgeType.CONNECTS_TO,
6702
+ provenance: import_types14.Provenance.EXTRACTED,
6703
+ confidence: (0, import_types14.confidenceForExtracted)("structural"),
6611
6704
  evidence: { file: evidenceFile }
6612
6705
  };
6613
6706
  if (!graph.hasEdge(edge.id)) {
@@ -6619,11 +6712,11 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
6619
6712
  const primary = allConfigs[0];
6620
6713
  service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
6621
6714
  const relPath = import_node_path24.default.relative(scanPath, primary.sourceFile);
6622
- const cfgId = (0, import_types13.configId)(relPath);
6715
+ const cfgId = (0, import_types14.configId)(relPath);
6623
6716
  if (!graph.hasNode(cfgId)) {
6624
6717
  const cfgNode = {
6625
6718
  id: cfgId,
6626
- type: import_types13.NodeType.ConfigNode,
6719
+ type: import_types14.NodeType.ConfigNode,
6627
6720
  name: import_node_path24.default.basename(primary.sourceFile),
6628
6721
  path: relPath,
6629
6722
  fileType: isConfigFile(import_node_path24.default.basename(primary.sourceFile)).fileType || "config"
@@ -6632,12 +6725,12 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
6632
6725
  nodesAdded++;
6633
6726
  }
6634
6727
  const cfgEdge = {
6635
- id: (0, import_types3.extractedEdgeId)(service.node.id, cfgId, import_types13.EdgeType.CONFIGURED_BY),
6728
+ id: (0, import_types3.extractedEdgeId)(service.node.id, cfgId, import_types14.EdgeType.CONFIGURED_BY),
6636
6729
  source: service.node.id,
6637
6730
  target: cfgId,
6638
- type: import_types13.EdgeType.CONFIGURED_BY,
6639
- provenance: import_types13.Provenance.EXTRACTED,
6640
- confidence: (0, import_types13.confidenceForExtracted)("structural"),
6731
+ type: import_types14.EdgeType.CONFIGURED_BY,
6732
+ provenance: import_types14.Provenance.EXTRACTED,
6733
+ confidence: (0, import_types14.confidenceForExtracted)("structural"),
6641
6734
  evidence: { file: toPosix(relPath) }
6642
6735
  };
6643
6736
  if (!graph.hasEdge(cfgEdge.id)) {
@@ -6669,7 +6762,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
6669
6762
  init_cjs_shims();
6670
6763
  var import_node_fs15 = require("fs");
6671
6764
  var import_node_path25 = __toESM(require("path"), 1);
6672
- var import_types14 = require("@neat.is/types");
6765
+ var import_types15 = require("@neat.is/types");
6673
6766
  async function walkConfigFiles(dir) {
6674
6767
  const out = [];
6675
6768
  async function walk6(current) {
@@ -6696,8 +6789,8 @@ async function addConfigNodes(graph, services, scanPath) {
6696
6789
  for (const file of configFiles) {
6697
6790
  const relPath = import_node_path25.default.relative(scanPath, file);
6698
6791
  const node = {
6699
- id: (0, import_types14.configId)(relPath),
6700
- type: import_types14.NodeType.ConfigNode,
6792
+ id: (0, import_types15.configId)(relPath),
6793
+ type: import_types15.NodeType.ConfigNode,
6701
6794
  name: import_node_path25.default.basename(file),
6702
6795
  path: relPath,
6703
6796
  fileType: isConfigFile(import_node_path25.default.basename(file)).fileType
@@ -6716,12 +6809,12 @@ async function addConfigNodes(graph, services, scanPath) {
6716
6809
  nodesAdded += fn;
6717
6810
  edgesAdded += fe;
6718
6811
  const edge = {
6719
- id: (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types14.EdgeType.CONFIGURED_BY),
6812
+ id: (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types15.EdgeType.CONFIGURED_BY),
6720
6813
  source: fileNodeId,
6721
6814
  target: node.id,
6722
- type: import_types14.EdgeType.CONFIGURED_BY,
6723
- provenance: import_types14.Provenance.EXTRACTED,
6724
- confidence: (0, import_types14.confidenceForExtracted)("structural"),
6815
+ type: import_types15.EdgeType.CONFIGURED_BY,
6816
+ provenance: import_types15.Provenance.EXTRACTED,
6817
+ confidence: (0, import_types15.confidenceForExtracted)("structural"),
6725
6818
  evidence: { file: relPath.split(import_node_path25.default.sep).join("/") }
6726
6819
  };
6727
6820
  if (!graph.hasEdge(edge.id)) {
@@ -6737,7 +6830,7 @@ async function addConfigNodes(graph, services, scanPath) {
6737
6830
  init_cjs_shims();
6738
6831
  var import_node_fs16 = require("fs");
6739
6832
  var import_node_path26 = __toESM(require("path"), 1);
6740
- var import_types15 = require("@neat.is/types");
6833
+ var import_types16 = require("@neat.is/types");
6741
6834
  var PROTO_EXTENSION = ".proto";
6742
6835
  function packageOf(content) {
6743
6836
  const m = content.match(/^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;/m);
@@ -6815,11 +6908,11 @@ async function addGrpcMethods(graph, services) {
6815
6908
  }
6816
6909
  if (methods.length === 0) continue;
6817
6910
  for (const method of methods) {
6818
- const mid = (0, import_types15.grpcMethodId)(method.rpcService, method.rpcMethod);
6911
+ const mid = (0, import_types16.grpcMethodId)(method.rpcService, method.rpcMethod);
6819
6912
  if (!graph.hasNode(mid)) {
6820
6913
  const node = {
6821
6914
  id: mid,
6822
- type: import_types15.NodeType.GrpcMethodNode,
6915
+ type: import_types16.NodeType.GrpcMethodNode,
6823
6916
  name: `${method.rpcService}/${method.rpcMethod}`,
6824
6917
  rpcService: method.rpcService,
6825
6918
  rpcMethod: method.rpcMethod,
@@ -6830,15 +6923,15 @@ async function addGrpcMethods(graph, services) {
6830
6923
  graph.addNode(mid, node);
6831
6924
  nodesAdded++;
6832
6925
  }
6833
- const containsId = (0, import_types15.extractedEdgeId)(service.node.id, mid, import_types15.EdgeType.CONTAINS);
6926
+ const containsId = (0, import_types16.extractedEdgeId)(service.node.id, mid, import_types16.EdgeType.CONTAINS);
6834
6927
  if (!graph.hasEdge(containsId)) {
6835
6928
  const edge = {
6836
6929
  id: containsId,
6837
6930
  source: service.node.id,
6838
6931
  target: mid,
6839
- type: import_types15.EdgeType.CONTAINS,
6840
- provenance: import_types15.Provenance.EXTRACTED,
6841
- confidence: (0, import_types15.confidenceForExtracted)("structural"),
6932
+ type: import_types16.EdgeType.CONTAINS,
6933
+ provenance: import_types16.Provenance.EXTRACTED,
6934
+ confidence: (0, import_types16.confidenceForExtracted)("structural"),
6842
6935
  evidence: {
6843
6936
  file: relFile,
6844
6937
  line: method.line,
@@ -6856,7 +6949,7 @@ async function addGrpcMethods(graph, services) {
6856
6949
 
6857
6950
  // src/extract/calls/index.ts
6858
6951
  init_cjs_shims();
6859
- var import_types27 = require("@neat.is/types");
6952
+ var import_types29 = require("@neat.is/types");
6860
6953
 
6861
6954
  // src/extract/calls/http.ts
6862
6955
  init_cjs_shims();
@@ -6864,7 +6957,7 @@ var import_node_path27 = __toESM(require("path"), 1);
6864
6957
  var import_tree_sitter5 = __toESM(require("tree-sitter"), 1);
6865
6958
  var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
6866
6959
  var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
6867
- var import_types16 = require("@neat.is/types");
6960
+ var import_types17 = require("@neat.is/types");
6868
6961
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
6869
6962
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
6870
6963
  function isInsideJsxExternalLink(node) {
@@ -6950,7 +7043,7 @@ async function addHttpCallEdges(graph, services) {
6950
7043
  const dedupKey = `${relFile}|${targetId}`;
6951
7044
  if (seen.has(dedupKey)) continue;
6952
7045
  seen.add(dedupKey);
6953
- const confidence = (0, import_types16.confidenceForExtracted)("url-literal-service-target");
7046
+ const confidence = (0, import_types17.confidenceForExtracted)("url-literal-service-target");
6954
7047
  const ev = {
6955
7048
  file: relFile,
6956
7049
  line: site.line,
@@ -6964,25 +7057,25 @@ async function addHttpCallEdges(graph, services) {
6964
7057
  );
6965
7058
  nodesAdded += n;
6966
7059
  edgesAdded += e;
6967
- if (!(0, import_types16.passesExtractedFloor)(confidence)) {
7060
+ if (!(0, import_types17.passesExtractedFloor)(confidence)) {
6968
7061
  noteExtractedDropped({
6969
7062
  source: fileNodeId,
6970
7063
  target: targetId,
6971
- type: import_types16.EdgeType.CALLS,
7064
+ type: import_types17.EdgeType.CALLS,
6972
7065
  confidence,
6973
7066
  confidenceKind: "url-literal-service-target",
6974
7067
  evidence: ev
6975
7068
  });
6976
7069
  continue;
6977
7070
  }
6978
- const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, targetId, import_types16.EdgeType.CALLS);
7071
+ const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, targetId, import_types17.EdgeType.CALLS);
6979
7072
  if (!graph.hasEdge(edgeId)) {
6980
7073
  const edge = {
6981
7074
  id: edgeId,
6982
7075
  source: fileNodeId,
6983
7076
  target: targetId,
6984
- type: import_types16.EdgeType.CALLS,
6985
- provenance: import_types16.Provenance.EXTRACTED,
7077
+ type: import_types17.EdgeType.CALLS,
7078
+ provenance: import_types17.Provenance.EXTRACTED,
6986
7079
  confidence,
6987
7080
  evidence: ev
6988
7081
  };
@@ -7000,7 +7093,7 @@ init_cjs_shims();
7000
7093
  var import_node_path28 = __toESM(require("path"), 1);
7001
7094
  var import_tree_sitter6 = __toESM(require("tree-sitter"), 1);
7002
7095
  var import_tree_sitter_javascript5 = __toESM(require("tree-sitter-javascript"), 1);
7003
- var import_types17 = require("@neat.is/types");
7096
+ var import_types18 = require("@neat.is/types");
7004
7097
  var PARSE_CHUNK5 = 16384;
7005
7098
  function parseSource5(parser, source) {
7006
7099
  return parser.parse(
@@ -7165,9 +7258,9 @@ function buildRouteIndex(graph) {
7165
7258
  const index = /* @__PURE__ */ new Map();
7166
7259
  graph.forEachNode((_id, attrs) => {
7167
7260
  const node = attrs;
7168
- if (node.type !== import_types17.NodeType.RouteNode) return;
7261
+ if (node.type !== import_types18.NodeType.RouteNode) return;
7169
7262
  const route = attrs;
7170
- const owner = (0, import_types17.serviceId)(route.service);
7263
+ const owner = (0, import_types18.serviceId)(route.service);
7171
7264
  const entry = {
7172
7265
  method: route.method.toUpperCase(),
7173
7266
  normalizedPath: normalizePathTemplate(route.pathTemplate),
@@ -7225,7 +7318,7 @@ async function addRouteCallEdges(graph, services) {
7225
7318
  );
7226
7319
  nodesAdded += n;
7227
7320
  edgesAdded += e;
7228
- const confidence = (0, import_types17.confidenceForExtracted)("verified-call-site");
7321
+ const confidence = (0, import_types18.confidenceForExtracted)("verified-call-site");
7229
7322
  const ev = {
7230
7323
  file: relFile,
7231
7324
  line: site.line,
@@ -7233,25 +7326,25 @@ async function addRouteCallEdges(graph, services) {
7233
7326
  method: site.method ?? match.method,
7234
7327
  pathTemplate: site.pathTemplate
7235
7328
  };
7236
- if (!(0, import_types17.passesExtractedFloor)(confidence)) {
7329
+ if (!(0, import_types18.passesExtractedFloor)(confidence)) {
7237
7330
  noteExtractedDropped({
7238
7331
  source: fileNodeId,
7239
7332
  target: match.routeNodeId,
7240
- type: import_types17.EdgeType.CALLS,
7333
+ type: import_types18.EdgeType.CALLS,
7241
7334
  confidence,
7242
7335
  confidenceKind: "verified-call-site",
7243
7336
  evidence: ev
7244
7337
  });
7245
7338
  continue;
7246
7339
  }
7247
- const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types17.EdgeType.CALLS);
7340
+ const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, match.routeNodeId, import_types18.EdgeType.CALLS);
7248
7341
  if (!graph.hasEdge(edgeId)) {
7249
7342
  const edge = {
7250
7343
  id: edgeId,
7251
7344
  source: fileNodeId,
7252
7345
  target: match.routeNodeId,
7253
- type: import_types17.EdgeType.CALLS,
7254
- provenance: import_types17.Provenance.EXTRACTED,
7346
+ type: import_types18.EdgeType.CALLS,
7347
+ provenance: import_types18.Provenance.EXTRACTED,
7255
7348
  confidence,
7256
7349
  evidence: ev
7257
7350
  };
@@ -7267,7 +7360,7 @@ async function addRouteCallEdges(graph, services) {
7267
7360
  // src/extract/calls/kafka.ts
7268
7361
  init_cjs_shims();
7269
7362
  var import_node_path29 = __toESM(require("path"), 1);
7270
- var import_types18 = require("@neat.is/types");
7363
+ var import_types19 = require("@neat.is/types");
7271
7364
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
7272
7365
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
7273
7366
  function findAll(re, text) {
@@ -7288,7 +7381,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
7288
7381
  seen.add(key);
7289
7382
  const line = lineOf(file.content, topic);
7290
7383
  out.push({
7291
- infraId: (0, import_types18.infraId)("kafka-topic", topic),
7384
+ infraId: (0, import_types19.infraId)("kafka-topic", topic),
7292
7385
  name: topic,
7293
7386
  kind: "kafka-topic",
7294
7387
  edgeType,
@@ -7311,7 +7404,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
7311
7404
  // src/extract/calls/redis.ts
7312
7405
  init_cjs_shims();
7313
7406
  var import_node_path30 = __toESM(require("path"), 1);
7314
- var import_types19 = require("@neat.is/types");
7407
+ var import_types20 = require("@neat.is/types");
7315
7408
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
7316
7409
  function redisEndpointsFromFile(file, serviceDir) {
7317
7410
  const out = [];
@@ -7324,7 +7417,7 @@ function redisEndpointsFromFile(file, serviceDir) {
7324
7417
  seen.add(host);
7325
7418
  const line = lineOf(file.content, host);
7326
7419
  out.push({
7327
- infraId: (0, import_types19.infraId)("redis", host),
7420
+ infraId: (0, import_types20.infraId)("redis", host),
7328
7421
  name: host,
7329
7422
  kind: "redis",
7330
7423
  edgeType: "CALLS",
@@ -7345,7 +7438,7 @@ function redisEndpointsFromFile(file, serviceDir) {
7345
7438
  // src/extract/calls/aws.ts
7346
7439
  init_cjs_shims();
7347
7440
  var import_node_path31 = __toESM(require("path"), 1);
7348
- var import_types20 = require("@neat.is/types");
7441
+ var import_types21 = require("@neat.is/types");
7349
7442
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
7350
7443
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
7351
7444
  function hasMarker(text, markers) {
@@ -7369,7 +7462,7 @@ function awsEndpointsFromFile(file, serviceDir) {
7369
7462
  seen.add(key);
7370
7463
  const line = lineOf(file.content, name);
7371
7464
  out.push({
7372
- infraId: (0, import_types20.infraId)(kind, name),
7465
+ infraId: (0, import_types21.infraId)(kind, name),
7373
7466
  name,
7374
7467
  kind,
7375
7468
  edgeType: "CALLS",
@@ -7404,7 +7497,7 @@ function awsEndpointsFromFile(file, serviceDir) {
7404
7497
  // src/extract/calls/grpc.ts
7405
7498
  init_cjs_shims();
7406
7499
  var import_node_path32 = __toESM(require("path"), 1);
7407
- var import_types21 = require("@neat.is/types");
7500
+ var import_types22 = require("@neat.is/types");
7408
7501
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
7409
7502
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
7410
7503
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -7453,7 +7546,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
7453
7546
  const { kind } = classified;
7454
7547
  const line = lineOf(file.content, m[0]);
7455
7548
  out.push({
7456
- infraId: (0, import_types21.infraId)(kind, name),
7549
+ infraId: (0, import_types22.infraId)(kind, name),
7457
7550
  name,
7458
7551
  kind,
7459
7552
  edgeType: "CALLS",
@@ -7474,7 +7567,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
7474
7567
  // src/extract/calls/supabase.ts
7475
7568
  init_cjs_shims();
7476
7569
  var import_node_path33 = __toESM(require("path"), 1);
7477
- var import_types22 = require("@neat.is/types");
7570
+ var import_types23 = require("@neat.is/types");
7478
7571
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
7479
7572
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
7480
7573
  var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
@@ -7522,7 +7615,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
7522
7615
  seen.add(name);
7523
7616
  const line = lineOf(file.content, m[0]);
7524
7617
  out.push({
7525
- infraId: (0, import_types22.infraId)("supabase", name),
7618
+ infraId: (0, import_types23.infraId)("supabase", name),
7526
7619
  name,
7527
7620
  kind: "supabase",
7528
7621
  edgeType: "CALLS",
@@ -7553,7 +7646,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
7553
7646
  seen.add(key);
7554
7647
  const line = lineOf(file.content, am[0]);
7555
7648
  out.push({
7556
- infraId: (0, import_types22.infraId)(kind, resource),
7649
+ infraId: (0, import_types23.infraId)(kind, resource),
7557
7650
  name: resource,
7558
7651
  kind,
7559
7652
  edgeType: "CALLS",
@@ -7572,7 +7665,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
7572
7665
  // src/extract/calls/mongoose.ts
7573
7666
  init_cjs_shims();
7574
7667
  var import_node_path34 = __toESM(require("path"), 1);
7575
- var import_types23 = require("@neat.is/types");
7668
+ var import_types24 = require("@neat.is/types");
7576
7669
  var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
7577
7670
  var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
7578
7671
  var PLURALIZE_DISABLED_RE = /\bpluralize\s*\(\s*(?:null|false)\s*\)/;
@@ -7717,7 +7810,7 @@ function collectModelDefs(content, pluralizeOn) {
7717
7810
  function endpoint(r, file, serviceDir, matchText) {
7718
7811
  const line = lineOf(file.content, matchText);
7719
7812
  return {
7720
- infraId: (0, import_types23.infraId)(r.kind, r.name),
7813
+ infraId: (0, import_types24.infraId)(r.kind, r.name),
7721
7814
  name: r.name,
7722
7815
  kind: r.kind,
7723
7816
  edgeType: "CALLS",
@@ -7870,7 +7963,7 @@ init_cjs_shims();
7870
7963
  var import_node_path35 = __toESM(require("path"), 1);
7871
7964
  var import_tree_sitter7 = __toESM(require("tree-sitter"), 1);
7872
7965
  var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
7873
- var import_types24 = require("@neat.is/types");
7966
+ var import_types25 = require("@neat.is/types");
7874
7967
  var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
7875
7968
  var PARSE_CHUNK6 = 16384;
7876
7969
  function makePyParser4() {
@@ -7930,20 +8023,57 @@ function walk3(node, visit) {
7930
8023
  visit(node);
7931
8024
  for (const c of namedChildren(node)) walk3(c, visit);
7932
8025
  }
8026
+ var COLUMN_BUILDERS = /* @__PURE__ */ new Set(["Column", "mapped_column"]);
8027
+ function isColumnBuilder(call) {
8028
+ const fn = call.childForFieldName("function");
8029
+ const t = fn?.text;
8030
+ if (!t) return false;
8031
+ const base = t.includes(".") ? t.slice(t.lastIndexOf(".") + 1) : t;
8032
+ return COLUMN_BUILDERS.has(base);
8033
+ }
8034
+ function firstPositionalString(call) {
8035
+ const args = call.childForFieldName("arguments");
8036
+ if (!args) return null;
8037
+ for (const arg of namedChildren(args)) {
8038
+ if (arg.type === "keyword_argument") continue;
8039
+ return arg.type === "string" ? pyStaticStringText2(arg) : null;
8040
+ }
8041
+ return null;
8042
+ }
8043
+ function columnsFromClassBody(body) {
8044
+ const out = [];
8045
+ const seen = /* @__PURE__ */ new Set();
8046
+ for (const stmt of namedChildren(body)) {
8047
+ if (stmt.type !== "expression_statement") continue;
8048
+ const assign = stmt.namedChild(0);
8049
+ if (assign?.type !== "assignment") continue;
8050
+ const left = assign.childForFieldName("left");
8051
+ if (left?.type !== "identifier") continue;
8052
+ const right = assign.childForFieldName("right");
8053
+ if (right?.type !== "call" || !isColumnBuilder(right)) continue;
8054
+ const name = firstPositionalString(right) ?? left.text;
8055
+ if (name && !seen.has(name)) {
8056
+ seen.add(name);
8057
+ out.push(name);
8058
+ }
8059
+ }
8060
+ return out;
8061
+ }
7933
8062
  function sqlalchemyEndpointsFromFile(file, serviceDir) {
7934
8063
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
7935
8064
  const tree = parseSource6(makePyParser4(), file.content);
7936
8065
  const out = [];
7937
8066
  const seen = /* @__PURE__ */ new Set();
7938
- const push = (name, line) => {
8067
+ const push = (name, line, columns) => {
7939
8068
  if (seen.has(name)) return;
7940
8069
  seen.add(name);
7941
8070
  out.push({
7942
- infraId: (0, import_types24.infraId)("sql-table", name),
8071
+ infraId: (0, import_types25.infraId)("sql-table", name),
7943
8072
  name,
7944
8073
  kind: "sql-table",
7945
8074
  edgeType: "CALLS",
7946
8075
  confidenceKind: "verified-call-site",
8076
+ ...columns && columns.length > 0 ? { columns } : {},
7947
8077
  evidence: {
7948
8078
  file: import_node_path35.default.relative(serviceDir, file.path),
7949
8079
  line,
@@ -7959,11 +8089,12 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
7959
8089
  const line = node.startPosition.row + 1;
7960
8090
  const explicit = explicitTablename(body);
7961
8091
  if (explicit === "computed") return;
8092
+ const columns = columnsFromClassBody(body);
7962
8093
  if (explicit) {
7963
- push(explicit.name, line);
8094
+ push(explicit.name, line, columns);
7964
8095
  return;
7965
8096
  }
7966
- if (extendsFlaskModel(node)) push(flaskSqlalchemyTableName(nameNode.text), line);
8097
+ if (extendsFlaskModel(node)) push(flaskSqlalchemyTableName(nameNode.text), line, columns);
7967
8098
  return;
7968
8099
  }
7969
8100
  if (node.type === "call") {
@@ -8045,7 +8176,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
8045
8176
  if (seen.has(key)) continue;
8046
8177
  seen.add(key);
8047
8178
  out.push({
8048
- infraId: (0, import_types24.infraId)("sql-table", t),
8179
+ infraId: (0, import_types25.infraId)("sql-table", t),
8049
8180
  name: t,
8050
8181
  kind: "sql-table",
8051
8182
  edgeType: "CALLS",
@@ -8066,7 +8197,7 @@ init_cjs_shims();
8066
8197
  var import_node_path36 = __toESM(require("path"), 1);
8067
8198
  var import_tree_sitter8 = __toESM(require("tree-sitter"), 1);
8068
8199
  var import_tree_sitter_python5 = __toESM(require("tree-sitter-python"), 1);
8069
- var import_types25 = require("@neat.is/types");
8200
+ var import_types26 = require("@neat.is/types");
8070
8201
  var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
8071
8202
  var PARSE_CHUNK7 = 16384;
8072
8203
  function makePyParser5() {
@@ -8146,7 +8277,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
8146
8277
  seen.add(table);
8147
8278
  const line = node.startPosition.row + 1;
8148
8279
  out.push({
8149
- infraId: (0, import_types25.infraId)("sql-table", table),
8280
+ infraId: (0, import_types26.infraId)("sql-table", table),
8150
8281
  name: table,
8151
8282
  kind: "sql-table",
8152
8283
  edgeType: "CALLS",
@@ -8157,12 +8288,129 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
8157
8288
  return out;
8158
8289
  }
8159
8290
 
8160
- // src/extract/calls/go.ts
8291
+ // src/extract/calls/drizzle.ts
8161
8292
  init_cjs_shims();
8162
- var import_node_path39 = __toESM(require("path"), 1);
8293
+ var import_node_path37 = __toESM(require("path"), 1);
8163
8294
  var import_tree_sitter9 = __toESM(require("tree-sitter"), 1);
8295
+ var import_tree_sitter_javascript6 = __toESM(require("tree-sitter-javascript"), 1);
8296
+ var import_types27 = require("@neat.is/types");
8297
+ var DRIZZLE_IMPORT_RE = /drizzle-orm/;
8298
+ var TABLE_BUILDERS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
8299
+ function parserForExt(ext) {
8300
+ const p = new import_tree_sitter9.default();
8301
+ p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript6.default);
8302
+ return p;
8303
+ }
8304
+ function namedChildren3(node) {
8305
+ const out = [];
8306
+ for (let i = 0; i < node.namedChildCount; i++) {
8307
+ const c = node.namedChild(i);
8308
+ if (c) out.push(c);
8309
+ }
8310
+ return out;
8311
+ }
8312
+ function stringLiteralText2(node) {
8313
+ if (!node || node.type !== "string") return null;
8314
+ for (const child of namedChildren3(node)) {
8315
+ if (child.type === "string_fragment") return child.text;
8316
+ }
8317
+ return "";
8318
+ }
8319
+ function firstStringArg(call) {
8320
+ const args = call.childForFieldName("arguments");
8321
+ if (!args) return null;
8322
+ for (const arg of namedChildren3(args)) {
8323
+ if (arg.type === "string") return stringLiteralText2(arg);
8324
+ return null;
8325
+ }
8326
+ return null;
8327
+ }
8328
+ function builderColumnName(value) {
8329
+ let node = value;
8330
+ while (node) {
8331
+ if (node.type === "call_expression") {
8332
+ const fn = node.childForFieldName("function");
8333
+ if (fn?.type === "identifier") return firstStringArg(node);
8334
+ if (fn?.type === "member_expression") {
8335
+ node = fn.childForFieldName("object");
8336
+ continue;
8337
+ }
8338
+ return null;
8339
+ }
8340
+ if (node.type === "member_expression") {
8341
+ node = node.childForFieldName("object");
8342
+ continue;
8343
+ }
8344
+ return null;
8345
+ }
8346
+ return null;
8347
+ }
8348
+ function keyName(key) {
8349
+ if (!key) return null;
8350
+ if (key.type === "property_identifier") return key.text;
8351
+ if (key.type === "string") return stringLiteralText2(key);
8352
+ return null;
8353
+ }
8354
+ function columnsFromObject(obj) {
8355
+ const out = [];
8356
+ const seen = /* @__PURE__ */ new Set();
8357
+ for (const child of namedChildren3(obj)) {
8358
+ if (child.type !== "pair") continue;
8359
+ const value = child.childForFieldName("value");
8360
+ const key = keyName(child.childForFieldName("key"));
8361
+ const name = (value ? builderColumnName(value) : null) ?? key;
8362
+ if (name && !seen.has(name)) {
8363
+ seen.add(name);
8364
+ out.push(name);
8365
+ }
8366
+ }
8367
+ return out;
8368
+ }
8369
+ function drizzleEndpointsFromFile(file, serviceDir) {
8370
+ if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
8371
+ const tree = parseSource2(parserForExt(import_node_path37.default.extname(file.path)), file.content);
8372
+ const out = [];
8373
+ const seen = /* @__PURE__ */ new Set();
8374
+ const walk6 = (node) => {
8375
+ if (node.type === "call_expression") {
8376
+ const fn = node.childForFieldName("function");
8377
+ if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
8378
+ const args = node.childForFieldName("arguments");
8379
+ const argNodes = args ? namedChildren3(args) : [];
8380
+ const tableName = stringLiteralText2(argNodes[0] ?? null);
8381
+ const obj = argNodes[1];
8382
+ if (tableName && obj?.type === "object" && !seen.has(tableName)) {
8383
+ seen.add(tableName);
8384
+ const columns = columnsFromObject(obj);
8385
+ const line = node.startPosition.row + 1;
8386
+ out.push({
8387
+ infraId: (0, import_types27.infraId)("sql-table", tableName),
8388
+ name: tableName,
8389
+ kind: "sql-table",
8390
+ edgeType: "CALLS",
8391
+ confidenceKind: "structural",
8392
+ columns,
8393
+ evidence: {
8394
+ file: import_node_path37.default.relative(serviceDir, file.path),
8395
+ line,
8396
+ snippet: snippet(file.content, line)
8397
+ }
8398
+ });
8399
+ }
8400
+ }
8401
+ }
8402
+ for (const c of namedChildren3(node)) walk6(c);
8403
+ };
8404
+ walk6(tree.rootNode);
8405
+ return out;
8406
+ }
8407
+
8408
+ // src/extract/calls/go.ts
8409
+ init_cjs_shims();
8410
+ var import_node_path40 = __toESM(require("path"), 1);
8411
+ var import_tree_sitter10 = __toESM(require("tree-sitter"), 1);
8164
8412
  var import_tree_sitter_go3 = __toESM(require("tree-sitter-go"), 1);
8165
- var import_types26 = require("@neat.is/types");
8413
+ var import_types28 = require("@neat.is/types");
8166
8414
  init_otel();
8167
8415
  var SQL_METHODS = /* @__PURE__ */ new Set(["Exec", "ExecContext", "Query", "QueryContext", "QueryRow", "QueryRowContext"]);
8168
8416
  var PARSE_CHUNK8 = 16384;
@@ -8174,8 +8422,8 @@ function walk5(node, visit) {
8174
8422
  }
8175
8423
  }
8176
8424
  function goSqlEndpointsFromFile(file, serviceDir) {
8177
- if (import_node_path39.default.extname(file.path) !== ".go") return [];
8178
- const parser = new import_tree_sitter9.default();
8425
+ if (import_node_path40.default.extname(file.path) !== ".go") return [];
8426
+ const parser = new import_tree_sitter10.default();
8179
8427
  parser.setLanguage(import_tree_sitter_go3.default);
8180
8428
  const tree = parser.parse(
8181
8429
  (index) => index >= file.content.length ? "" : file.content.slice(index, index + PARSE_CHUNK8)
@@ -8194,12 +8442,12 @@ function goSqlEndpointsFromFile(file, serviceDir) {
8194
8442
  if (!table) return;
8195
8443
  const line = node.startPosition.row + 1;
8196
8444
  out.push({
8197
- infraId: (0, import_types26.infraId)("sql-table", table),
8445
+ infraId: (0, import_types28.infraId)("sql-table", table),
8198
8446
  name: table,
8199
8447
  kind: "sql-table",
8200
8448
  edgeType: "CALLS",
8201
8449
  confidenceKind: "verified-call-site",
8202
- evidence: { file: toPosix(import_node_path39.default.relative(serviceDir, file.path)), line, snippet: snippet(file.content, line) }
8450
+ evidence: { file: toPosix(import_node_path40.default.relative(serviceDir, file.path)), line, snippet: snippet(file.content, line) }
8203
8451
  });
8204
8452
  });
8205
8453
  return out;
@@ -8209,11 +8457,11 @@ function goSqlEndpointsFromFile(file, serviceDir) {
8209
8457
  function edgeTypeFromEndpoint(ep) {
8210
8458
  switch (ep.edgeType) {
8211
8459
  case "PUBLISHES_TO":
8212
- return import_types27.EdgeType.PUBLISHES_TO;
8460
+ return import_types29.EdgeType.PUBLISHES_TO;
8213
8461
  case "CONSUMES_FROM":
8214
- return import_types27.EdgeType.CONSUMES_FROM;
8462
+ return import_types29.EdgeType.CONSUMES_FROM;
8215
8463
  default:
8216
- return import_types27.EdgeType.CALLS;
8464
+ return import_types29.EdgeType.CALLS;
8217
8465
  }
8218
8466
  }
8219
8467
  function isAwsKind(kind) {
@@ -8239,6 +8487,7 @@ async function addExternalEndpointEdges(graph, services) {
8239
8487
  endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
8240
8488
  endpoints.push(...sqlalchemyEndpointsFromFile(maskedFile, service.dir));
8241
8489
  endpoints.push(...djangoOrmEndpointsFromFile(maskedFile, service.dir));
8490
+ endpoints.push(...drizzleEndpointsFromFile(maskedFile, service.dir));
8242
8491
  try {
8243
8492
  endpoints.push(...goSqlEndpointsFromFile(maskedFile, service.dir));
8244
8493
  } catch (err) {
@@ -8253,7 +8502,7 @@ async function addExternalEndpointEdges(graph, services) {
8253
8502
  if (!graph.hasNode(ep.infraId)) {
8254
8503
  const node = {
8255
8504
  id: ep.infraId,
8256
- type: import_types27.NodeType.InfraNode,
8505
+ type: import_types29.NodeType.InfraNode,
8257
8506
  name: ep.name,
8258
8507
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
8259
8508
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -8264,8 +8513,22 @@ async function addExternalEndpointEdges(graph, services) {
8264
8513
  graph.addNode(node.id, node);
8265
8514
  nodesAdded++;
8266
8515
  }
8516
+ if (ep.columns && ep.columns.length > 0) {
8517
+ const node = graph.getNodeAttributes(ep.infraId);
8518
+ if (node.type === import_types29.NodeType.InfraNode) {
8519
+ graph.replaceNodeAttributes(ep.infraId, {
8520
+ ...node,
8521
+ columns: foldColumns(
8522
+ node.columns,
8523
+ ep.columns,
8524
+ import_types29.Provenance.EXTRACTED,
8525
+ (0, import_types29.confidenceForExtracted)(ep.confidenceKind)
8526
+ )
8527
+ });
8528
+ }
8529
+ }
8267
8530
  const edgeType = edgeTypeFromEndpoint(ep);
8268
- const confidence = (0, import_types27.confidenceForExtracted)(ep.confidenceKind);
8531
+ const confidence = (0, import_types29.confidenceForExtracted)(ep.confidenceKind);
8269
8532
  const relFile = toPosix(ep.evidence.file);
8270
8533
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
8271
8534
  graph,
@@ -8275,7 +8538,7 @@ async function addExternalEndpointEdges(graph, services) {
8275
8538
  );
8276
8539
  nodesAdded += n;
8277
8540
  edgesAdded += e;
8278
- if (!(0, import_types27.passesExtractedFloor)(confidence)) {
8541
+ if (!(0, import_types29.passesExtractedFloor)(confidence)) {
8279
8542
  noteExtractedDropped({
8280
8543
  source: fileNodeId,
8281
8544
  target: ep.infraId,
@@ -8295,7 +8558,7 @@ async function addExternalEndpointEdges(graph, services) {
8295
8558
  source: fileNodeId,
8296
8559
  target: ep.infraId,
8297
8560
  type: edgeType,
8298
- provenance: import_types27.Provenance.EXTRACTED,
8561
+ provenance: import_types29.Provenance.EXTRACTED,
8299
8562
  confidence,
8300
8563
  evidence: ep.evidence
8301
8564
  };
@@ -8321,16 +8584,16 @@ init_cjs_shims();
8321
8584
 
8322
8585
  // src/extract/infra/docker-compose.ts
8323
8586
  init_cjs_shims();
8324
- var import_node_path40 = __toESM(require("path"), 1);
8325
- var import_types29 = require("@neat.is/types");
8587
+ var import_node_path41 = __toESM(require("path"), 1);
8588
+ var import_types31 = require("@neat.is/types");
8326
8589
 
8327
8590
  // src/extract/infra/shared.ts
8328
8591
  init_cjs_shims();
8329
- var import_types28 = require("@neat.is/types");
8592
+ var import_types30 = require("@neat.is/types");
8330
8593
  function makeInfraNode(kind, name, provider = "self", extras) {
8331
8594
  return {
8332
- id: (0, import_types28.infraId)(kind, name),
8333
- type: import_types28.NodeType.InfraNode,
8595
+ id: (0, import_types30.infraId)(kind, name),
8596
+ type: import_types30.NodeType.InfraNode,
8334
8597
  name,
8335
8598
  provider,
8336
8599
  kind,
@@ -8374,8 +8637,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
8374
8637
  source: anchorId,
8375
8638
  target: node.id,
8376
8639
  type: edgeType,
8377
- provenance: import_types28.Provenance.EXTRACTED,
8378
- confidence: (0, import_types28.confidenceForExtracted)("structural"),
8640
+ provenance: import_types30.Provenance.EXTRACTED,
8641
+ confidence: (0, import_types30.confidenceForExtracted)("structural"),
8379
8642
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
8380
8643
  };
8381
8644
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -8392,7 +8655,7 @@ function dependsOnList(value) {
8392
8655
  }
8393
8656
  function serviceNameToServiceNode(name, services) {
8394
8657
  for (const s of services) {
8395
- if (s.node.name === name || import_node_path40.default.basename(s.dir) === name) return s.node.id;
8658
+ if (s.node.name === name || import_node_path41.default.basename(s.dir) === name) return s.node.id;
8396
8659
  }
8397
8660
  return null;
8398
8661
  }
@@ -8401,7 +8664,7 @@ async function addComposeInfra(graph, scanPath, services) {
8401
8664
  let edgesAdded = 0;
8402
8665
  let composePath = null;
8403
8666
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
8404
- const abs = import_node_path40.default.join(scanPath, name);
8667
+ const abs = import_node_path41.default.join(scanPath, name);
8405
8668
  if (await exists(abs)) {
8406
8669
  composePath = abs;
8407
8670
  break;
@@ -8414,13 +8677,13 @@ async function addComposeInfra(graph, scanPath, services) {
8414
8677
  } catch (err) {
8415
8678
  recordExtractionError(
8416
8679
  "infra docker-compose",
8417
- import_node_path40.default.relative(scanPath, composePath),
8680
+ import_node_path41.default.relative(scanPath, composePath),
8418
8681
  err
8419
8682
  );
8420
8683
  return { nodesAdded, edgesAdded };
8421
8684
  }
8422
8685
  if (!compose?.services) return { nodesAdded, edgesAdded };
8423
- const evidenceFile = import_node_path40.default.relative(scanPath, composePath).split(import_node_path40.default.sep).join("/");
8686
+ const evidenceFile = import_node_path41.default.relative(scanPath, composePath).split(import_node_path41.default.sep).join("/");
8424
8687
  const composeNameToNodeId = /* @__PURE__ */ new Map();
8425
8688
  for (const [composeName, svc] of Object.entries(compose.services)) {
8426
8689
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -8442,15 +8705,15 @@ async function addComposeInfra(graph, scanPath, services) {
8442
8705
  for (const dep of dependsOnList(svc.depends_on)) {
8443
8706
  const targetId = composeNameToNodeId.get(dep);
8444
8707
  if (!targetId) continue;
8445
- const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types29.EdgeType.DEPENDS_ON);
8708
+ const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types31.EdgeType.DEPENDS_ON);
8446
8709
  if (graph.hasEdge(edgeId)) continue;
8447
8710
  const edge = {
8448
8711
  id: edgeId,
8449
8712
  source: sourceId,
8450
8713
  target: targetId,
8451
- type: import_types29.EdgeType.DEPENDS_ON,
8452
- provenance: import_types29.Provenance.EXTRACTED,
8453
- confidence: (0, import_types29.confidenceForExtracted)("structural"),
8714
+ type: import_types31.EdgeType.DEPENDS_ON,
8715
+ provenance: import_types31.Provenance.EXTRACTED,
8716
+ confidence: (0, import_types31.confidenceForExtracted)("structural"),
8454
8717
  evidence: { file: evidenceFile }
8455
8718
  };
8456
8719
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -8462,9 +8725,9 @@ async function addComposeInfra(graph, scanPath, services) {
8462
8725
 
8463
8726
  // src/extract/infra/dockerfile.ts
8464
8727
  init_cjs_shims();
8465
- var import_node_path41 = __toESM(require("path"), 1);
8728
+ var import_node_path42 = __toESM(require("path"), 1);
8466
8729
  var import_node_fs17 = require("fs");
8467
- var import_types30 = require("@neat.is/types");
8730
+ var import_types32 = require("@neat.is/types");
8468
8731
  function readDockerfile(content) {
8469
8732
  let image = null;
8470
8733
  const ports = [];
@@ -8493,7 +8756,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
8493
8756
  let nodesAdded = 0;
8494
8757
  let edgesAdded = 0;
8495
8758
  for (const service of services) {
8496
- const dockerfilePath = import_node_path41.default.join(service.dir, "Dockerfile");
8759
+ const dockerfilePath = import_node_path42.default.join(service.dir, "Dockerfile");
8497
8760
  if (!await exists(dockerfilePath)) continue;
8498
8761
  let content;
8499
8762
  try {
@@ -8501,7 +8764,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
8501
8764
  } catch (err) {
8502
8765
  recordExtractionError(
8503
8766
  "infra dockerfile",
8504
- import_node_path41.default.relative(scanPath, dockerfilePath),
8767
+ import_node_path42.default.relative(scanPath, dockerfilePath),
8505
8768
  err
8506
8769
  );
8507
8770
  continue;
@@ -8513,8 +8776,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
8513
8776
  graph.addNode(node.id, node);
8514
8777
  nodesAdded++;
8515
8778
  }
8516
- const relDockerfile = toPosix(import_node_path41.default.relative(service.dir, dockerfilePath));
8517
- const evidenceFile = toPosix(import_node_path41.default.relative(scanPath, dockerfilePath));
8779
+ const relDockerfile = toPosix(import_node_path42.default.relative(service.dir, dockerfilePath));
8780
+ const evidenceFile = toPosix(import_node_path42.default.relative(scanPath, dockerfilePath));
8518
8781
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
8519
8782
  graph,
8520
8783
  service.pkg.name,
@@ -8523,15 +8786,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
8523
8786
  );
8524
8787
  nodesAdded += fn;
8525
8788
  edgesAdded += fe;
8526
- const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types30.EdgeType.RUNS_ON);
8789
+ const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types32.EdgeType.RUNS_ON);
8527
8790
  if (!graph.hasEdge(edgeId)) {
8528
8791
  const edge = {
8529
8792
  id: edgeId,
8530
8793
  source: fileNodeId,
8531
8794
  target: node.id,
8532
- type: import_types30.EdgeType.RUNS_ON,
8533
- provenance: import_types30.Provenance.EXTRACTED,
8534
- confidence: (0, import_types30.confidenceForExtracted)("structural"),
8795
+ type: import_types32.EdgeType.RUNS_ON,
8796
+ provenance: import_types32.Provenance.EXTRACTED,
8797
+ confidence: (0, import_types32.confidenceForExtracted)("structural"),
8535
8798
  evidence: {
8536
8799
  file: evidenceFile,
8537
8800
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -8546,15 +8809,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
8546
8809
  graph.addNode(portNode.id, portNode);
8547
8810
  nodesAdded++;
8548
8811
  }
8549
- const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types30.EdgeType.CONNECTS_TO);
8812
+ const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types32.EdgeType.CONNECTS_TO);
8550
8813
  if (graph.hasEdge(portEdgeId)) continue;
8551
8814
  const portEdge = {
8552
8815
  id: portEdgeId,
8553
8816
  source: fileNodeId,
8554
8817
  target: portNode.id,
8555
- type: import_types30.EdgeType.CONNECTS_TO,
8556
- provenance: import_types30.Provenance.EXTRACTED,
8557
- confidence: (0, import_types30.confidenceForExtracted)("structural"),
8818
+ type: import_types32.EdgeType.CONNECTS_TO,
8819
+ provenance: import_types32.Provenance.EXTRACTED,
8820
+ confidence: (0, import_types32.confidenceForExtracted)("structural"),
8558
8821
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
8559
8822
  };
8560
8823
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -8567,8 +8830,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
8567
8830
  // src/extract/infra/terraform.ts
8568
8831
  init_cjs_shims();
8569
8832
  var import_node_fs18 = require("fs");
8570
- var import_node_path42 = __toESM(require("path"), 1);
8571
- var import_types31 = require("@neat.is/types");
8833
+ var import_node_path43 = __toESM(require("path"), 1);
8834
+ var import_types33 = require("@neat.is/types");
8572
8835
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
8573
8836
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
8574
8837
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -8578,11 +8841,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
8578
8841
  for (const entry of entries) {
8579
8842
  if (entry.isDirectory()) {
8580
8843
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
8581
- const child = import_node_path42.default.join(start, entry.name);
8844
+ const child = import_node_path43.default.join(start, entry.name);
8582
8845
  if (await isPythonVenvDir(child)) continue;
8583
8846
  out.push(...await walkTfFiles(child, depth + 1, max));
8584
8847
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
8585
- out.push(import_node_path42.default.join(start, entry.name));
8848
+ out.push(import_node_path43.default.join(start, entry.name));
8586
8849
  }
8587
8850
  }
8588
8851
  return out;
@@ -8614,7 +8877,7 @@ async function addTerraformResources(graph, scanPath) {
8614
8877
  const files = await walkTfFiles(scanPath);
8615
8878
  for (const file of files) {
8616
8879
  const content = await import_node_fs18.promises.readFile(file, "utf8");
8617
- const evidenceFile = toPosix(import_node_path42.default.relative(scanPath, file));
8880
+ const evidenceFile = toPosix(import_node_path43.default.relative(scanPath, file));
8618
8881
  const resources = [];
8619
8882
  const byKey = /* @__PURE__ */ new Map();
8620
8883
  RESOURCE_RE.lastIndex = 0;
@@ -8649,16 +8912,16 @@ async function addTerraformResources(graph, scanPath) {
8649
8912
  if (!target) continue;
8650
8913
  if (seen.has(target.nodeId)) continue;
8651
8914
  seen.add(target.nodeId);
8652
- const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types31.EdgeType.DEPENDS_ON);
8915
+ const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types33.EdgeType.DEPENDS_ON);
8653
8916
  if (graph.hasEdge(edgeId)) continue;
8654
8917
  const line = lineAt2(content, resource.bodyOffset + ref.index);
8655
8918
  const edge = {
8656
8919
  id: edgeId,
8657
8920
  source: resource.nodeId,
8658
8921
  target: target.nodeId,
8659
- type: import_types31.EdgeType.DEPENDS_ON,
8660
- provenance: import_types31.Provenance.EXTRACTED,
8661
- confidence: (0, import_types31.confidenceForExtracted)("structural"),
8922
+ type: import_types33.EdgeType.DEPENDS_ON,
8923
+ provenance: import_types33.Provenance.EXTRACTED,
8924
+ confidence: (0, import_types33.confidenceForExtracted)("structural"),
8662
8925
  evidence: { file: evidenceFile, line, snippet: key }
8663
8926
  };
8664
8927
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -8672,7 +8935,7 @@ async function addTerraformResources(graph, scanPath) {
8672
8935
  // src/extract/infra/k8s.ts
8673
8936
  init_cjs_shims();
8674
8937
  var import_node_fs19 = require("fs");
8675
- var import_node_path43 = __toESM(require("path"), 1);
8938
+ var import_node_path44 = __toESM(require("path"), 1);
8676
8939
  var import_yaml3 = require("yaml");
8677
8940
  var K8S_KIND_TO_INFRA_KIND = {
8678
8941
  Service: "k8s-service",
@@ -8690,11 +8953,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
8690
8953
  for (const entry of entries) {
8691
8954
  if (entry.isDirectory()) {
8692
8955
  if (IGNORED_DIRS.has(entry.name)) continue;
8693
- const child = import_node_path43.default.join(start, entry.name);
8956
+ const child = import_node_path44.default.join(start, entry.name);
8694
8957
  if (await isPythonVenvDir(child)) continue;
8695
8958
  out.push(...await walkYamlFiles2(child, depth + 1, max));
8696
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path43.default.extname(entry.name))) {
8697
- out.push(import_node_path43.default.join(start, entry.name));
8959
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path44.default.extname(entry.name))) {
8960
+ out.push(import_node_path44.default.join(start, entry.name));
8698
8961
  }
8699
8962
  }
8700
8963
  return out;
@@ -8728,13 +8991,13 @@ async function addK8sResources(graph, scanPath) {
8728
8991
  // src/extract/infra/cloudflare.ts
8729
8992
  init_cjs_shims();
8730
8993
  var import_node_fs20 = require("fs");
8731
- var import_node_path44 = __toESM(require("path"), 1);
8994
+ var import_node_path45 = __toESM(require("path"), 1);
8732
8995
  var import_smol_toml2 = require("smol-toml");
8733
- var import_types32 = require("@neat.is/types");
8996
+ var import_types34 = require("@neat.is/types");
8734
8997
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
8735
8998
  async function readWranglerConfig(dir) {
8736
8999
  for (const filename of WRANGLER_FILENAMES) {
8737
- const abs = import_node_path44.default.join(dir, filename);
9000
+ const abs = import_node_path45.default.join(dir, filename);
8738
9001
  if (!await exists(abs)) continue;
8739
9002
  const raw = await import_node_fs20.promises.readFile(abs, "utf8");
8740
9003
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -8778,8 +9041,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
8778
9041
  source: anchorId,
8779
9042
  target: node.id,
8780
9043
  type: edgeType,
8781
- provenance: import_types32.Provenance.EXTRACTED,
8782
- confidence: (0, import_types32.confidenceForExtracted)("structural"),
9044
+ provenance: import_types34.Provenance.EXTRACTED,
9045
+ confidence: (0, import_types34.confidenceForExtracted)("structural"),
8783
9046
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
8784
9047
  };
8785
9048
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -8797,11 +9060,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8797
9060
  try {
8798
9061
  read = await readWranglerConfig(service.dir);
8799
9062
  } catch (err) {
8800
- recordExtractionError("infra cloudflare", import_node_path44.default.relative(scanPath, service.dir), err);
9063
+ recordExtractionError("infra cloudflare", import_node_path45.default.relative(scanPath, service.dir), err);
8801
9064
  continue;
8802
9065
  }
8803
9066
  if (!read || !read.config.name) continue;
8804
- const evidenceFile = toPosix(import_node_path44.default.relative(scanPath, import_node_path44.default.join(service.dir, read.relFile)));
9067
+ const evidenceFile = toPosix(import_node_path45.default.relative(scanPath, import_node_path45.default.join(service.dir, read.relFile)));
8805
9068
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
8806
9069
  }
8807
9070
  for (const worker of discovered) {
@@ -8813,7 +9076,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8813
9076
  }
8814
9077
  let anchorId = service.node.id;
8815
9078
  if (config.main) {
8816
- const entryRelPath = toPosix(import_node_path44.default.normalize(config.main));
9079
+ const entryRelPath = toPosix(import_node_path45.default.normalize(config.main));
8817
9080
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
8818
9081
  graph,
8819
9082
  service.pkg.name,
@@ -8840,15 +9103,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8840
9103
  nodesAdded++;
8841
9104
  }
8842
9105
  if (runtimeNode.id !== anchorId) {
8843
- const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types32.EdgeType.RUNS_ON);
9106
+ const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types34.EdgeType.RUNS_ON);
8844
9107
  if (!graph.hasEdge(runsOnId)) {
8845
9108
  const edge = {
8846
9109
  id: runsOnId,
8847
9110
  source: anchorId,
8848
9111
  target: runtimeNode.id,
8849
- type: import_types32.EdgeType.RUNS_ON,
8850
- provenance: import_types32.Provenance.EXTRACTED,
8851
- confidence: (0, import_types32.confidenceForExtracted)("structural"),
9112
+ type: import_types34.EdgeType.RUNS_ON,
9113
+ provenance: import_types34.Provenance.EXTRACTED,
9114
+ confidence: (0, import_types34.confidenceForExtracted)("structural"),
8852
9115
  evidence: {
8853
9116
  file: evidenceFile,
8854
9117
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -8862,7 +9125,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8862
9125
  const result = addResourceEdge(
8863
9126
  graph,
8864
9127
  anchorId,
8865
- import_types32.EdgeType.CONNECTS_TO,
9128
+ import_types34.EdgeType.CONNECTS_TO,
8866
9129
  "cloudflare-route",
8867
9130
  route,
8868
9131
  evidenceFile,
@@ -8886,7 +9149,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8886
9149
  const result = addResourceEdge(
8887
9150
  graph,
8888
9151
  anchorId,
8889
- import_types32.EdgeType.DEPENDS_ON,
9152
+ import_types34.EdgeType.DEPENDS_ON,
8890
9153
  group.kind,
8891
9154
  name,
8892
9155
  evidenceFile,
@@ -8900,7 +9163,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8900
9163
  const result = addResourceEdge(
8901
9164
  graph,
8902
9165
  anchorId,
8903
- import_types32.EdgeType.DEPENDS_ON,
9166
+ import_types34.EdgeType.DEPENDS_ON,
8904
9167
  "cloudflare-cron",
8905
9168
  cron,
8906
9169
  evidenceFile,
@@ -8913,7 +9176,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8913
9176
  const result = addResourceEdge(
8914
9177
  graph,
8915
9178
  anchorId,
8916
- import_types32.EdgeType.DEPENDS_ON,
9179
+ import_types34.EdgeType.DEPENDS_ON,
8917
9180
  "cloudflare-env-var",
8918
9181
  varName,
8919
9182
  evidenceFile,
@@ -8926,15 +9189,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8926
9189
  if (!svc.service) continue;
8927
9190
  const target = workerIndex.get(svc.service);
8928
9191
  if (target && target.anchorId !== anchorId) {
8929
- const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types32.EdgeType.CALLS);
9192
+ const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types34.EdgeType.CALLS);
8930
9193
  if (!graph.hasEdge(edgeId)) {
8931
9194
  const edge = {
8932
9195
  id: edgeId,
8933
9196
  source: anchorId,
8934
9197
  target: target.anchorId,
8935
- type: import_types32.EdgeType.CALLS,
8936
- provenance: import_types32.Provenance.EXTRACTED,
8937
- confidence: (0, import_types32.confidenceForExtracted)("structural"),
9198
+ type: import_types34.EdgeType.CALLS,
9199
+ provenance: import_types34.Provenance.EXTRACTED,
9200
+ confidence: (0, import_types34.confidenceForExtracted)("structural"),
8938
9201
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
8939
9202
  };
8940
9203
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -8945,7 +9208,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8945
9208
  const result = addResourceEdge(
8946
9209
  graph,
8947
9210
  anchorId,
8948
- import_types32.EdgeType.DEPENDS_ON,
9211
+ import_types34.EdgeType.DEPENDS_ON,
8949
9212
  "cloudflare-service-binding",
8950
9213
  svc.service,
8951
9214
  evidenceFile,
@@ -8961,12 +9224,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
8961
9224
  // src/extract/infra/vercel.ts
8962
9225
  init_cjs_shims();
8963
9226
  var import_node_fs21 = require("fs");
8964
- var import_node_path45 = __toESM(require("path"), 1);
8965
- var import_types33 = require("@neat.is/types");
9227
+ var import_node_path46 = __toESM(require("path"), 1);
9228
+ var import_types35 = require("@neat.is/types");
8966
9229
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
8967
9230
  async function readVercelConfig(dir) {
8968
9231
  for (const filename of VERCEL_CONFIG_FILENAMES) {
8969
- const abs = import_node_path45.default.join(dir, filename);
9232
+ const abs = import_node_path46.default.join(dir, filename);
8970
9233
  if (!await exists(abs)) continue;
8971
9234
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
8972
9235
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -8975,7 +9238,7 @@ async function readVercelConfig(dir) {
8975
9238
  return null;
8976
9239
  }
8977
9240
  async function readLinkedProjectName(dir) {
8978
- const abs = import_node_path45.default.join(dir, ".vercel", "project.json");
9241
+ const abs = import_node_path46.default.join(dir, ".vercel", "project.json");
8979
9242
  if (!await exists(abs)) return void 0;
8980
9243
  const parsed = JSON.parse(await import_node_fs21.promises.readFile(abs, "utf8"));
8981
9244
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -8993,7 +9256,7 @@ async function addVercelServices(graph, services, scanPath) {
8993
9256
  read = await readVercelConfig(service.dir);
8994
9257
  projectName = await readLinkedProjectName(service.dir);
8995
9258
  } catch (err) {
8996
- recordExtractionError("infra vercel", import_node_path45.default.relative(scanPath, service.dir), err);
9259
+ recordExtractionError("infra vercel", import_node_path46.default.relative(scanPath, service.dir), err);
8997
9260
  continue;
8998
9261
  }
8999
9262
  if (!read && !projectName) continue;
@@ -9009,7 +9272,7 @@ async function addVercelServices(graph, services, scanPath) {
9009
9272
  const anchorId = service.node.id;
9010
9273
  if (!read) continue;
9011
9274
  const { config, relFile, raw } = read;
9012
- const evidenceFile = toPosix(import_node_path45.default.relative(scanPath, import_node_path45.default.join(service.dir, relFile)));
9275
+ const evidenceFile = toPosix(import_node_path46.default.relative(scanPath, import_node_path46.default.join(service.dir, relFile)));
9013
9276
  const add = (edgeType, kind, name) => {
9014
9277
  if (!name) return;
9015
9278
  const result = emitPlatformResourceEdge(
@@ -9025,12 +9288,12 @@ async function addVercelServices(graph, services, scanPath) {
9025
9288
  nodesAdded += result.nodesAdded;
9026
9289
  edgesAdded += result.edgesAdded;
9027
9290
  };
9028
- add(import_types33.EdgeType.RUNS_ON, "vercel", "vercel");
9029
- for (const cron of config.crons ?? []) add(import_types33.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
9030
- for (const varName of Object.keys(config.env ?? {})) add(import_types33.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
9031
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types33.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
9291
+ add(import_types35.EdgeType.RUNS_ON, "vercel", "vercel");
9292
+ for (const cron of config.crons ?? []) add(import_types35.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
9293
+ for (const varName of Object.keys(config.env ?? {})) add(import_types35.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
9294
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types35.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
9032
9295
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
9033
- add(import_types33.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
9296
+ add(import_types35.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
9034
9297
  }
9035
9298
  }
9036
9299
  return { nodesAdded, edgesAdded };
@@ -9039,13 +9302,13 @@ async function addVercelServices(graph, services, scanPath) {
9039
9302
  // src/extract/infra/railway.ts
9040
9303
  init_cjs_shims();
9041
9304
  var import_node_fs22 = require("fs");
9042
- var import_node_path46 = __toESM(require("path"), 1);
9305
+ var import_node_path47 = __toESM(require("path"), 1);
9043
9306
  var import_smol_toml3 = require("smol-toml");
9044
- var import_types34 = require("@neat.is/types");
9307
+ var import_types36 = require("@neat.is/types");
9045
9308
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
9046
9309
  async function readRailwayConfig(dir) {
9047
9310
  for (const filename of RAILWAY_FILENAMES) {
9048
- const abs = import_node_path46.default.join(dir, filename);
9311
+ const abs = import_node_path47.default.join(dir, filename);
9049
9312
  if (!await exists(abs)) continue;
9050
9313
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
9051
9314
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -9061,7 +9324,7 @@ async function addRailwayServices(graph, services, scanPath) {
9061
9324
  try {
9062
9325
  read = await readRailwayConfig(service.dir);
9063
9326
  } catch (err) {
9064
- recordExtractionError("infra railway", import_node_path46.default.relative(scanPath, service.dir), err);
9327
+ recordExtractionError("infra railway", import_node_path47.default.relative(scanPath, service.dir), err);
9065
9328
  continue;
9066
9329
  }
9067
9330
  if (!read) continue;
@@ -9071,7 +9334,7 @@ async function addRailwayServices(graph, services, scanPath) {
9071
9334
  }
9072
9335
  const anchorId = service.node.id;
9073
9336
  const { config, relFile, raw } = read;
9074
- const evidenceFile = toPosix(import_node_path46.default.relative(scanPath, import_node_path46.default.join(service.dir, relFile)));
9337
+ const evidenceFile = toPosix(import_node_path47.default.relative(scanPath, import_node_path47.default.join(service.dir, relFile)));
9075
9338
  const add = (edgeType, kind, name) => {
9076
9339
  if (!name) return;
9077
9340
  const result = emitPlatformResourceEdge(
@@ -9087,9 +9350,9 @@ async function addRailwayServices(graph, services, scanPath) {
9087
9350
  nodesAdded += result.nodesAdded;
9088
9351
  edgesAdded += result.edgesAdded;
9089
9352
  };
9090
- add(import_types34.EdgeType.RUNS_ON, "railway", "railway");
9091
- add(import_types34.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
9092
- add(import_types34.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
9353
+ add(import_types36.EdgeType.RUNS_ON, "railway", "railway");
9354
+ add(import_types36.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
9355
+ add(import_types36.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
9093
9356
  }
9094
9357
  return { nodesAdded, edgesAdded };
9095
9358
  }
@@ -9097,12 +9360,12 @@ async function addRailwayServices(graph, services, scanPath) {
9097
9360
  // src/extract/infra/supabase.ts
9098
9361
  init_cjs_shims();
9099
9362
  var import_node_fs23 = require("fs");
9100
- var import_node_path47 = __toESM(require("path"), 1);
9363
+ var import_node_path48 = __toESM(require("path"), 1);
9101
9364
  var import_smol_toml4 = require("smol-toml");
9102
- var import_types35 = require("@neat.is/types");
9365
+ var import_types37 = require("@neat.is/types");
9103
9366
  async function readSupabaseConfig(dir) {
9104
- const relFile = import_node_path47.default.join("supabase", "config.toml");
9105
- const abs = import_node_path47.default.join(dir, relFile);
9367
+ const relFile = import_node_path48.default.join("supabase", "config.toml");
9368
+ const abs = import_node_path48.default.join(dir, relFile);
9106
9369
  if (!await exists(abs)) return null;
9107
9370
  const raw = await import_node_fs23.promises.readFile(abs, "utf8");
9108
9371
  const config = (0, import_smol_toml4.parse)(raw);
@@ -9116,7 +9379,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
9116
9379
  try {
9117
9380
  read = await readSupabaseConfig(service.dir);
9118
9381
  } catch (err) {
9119
- recordExtractionError("infra supabase", import_node_path47.default.relative(scanPath, service.dir), err);
9382
+ recordExtractionError("infra supabase", import_node_path48.default.relative(scanPath, service.dir), err);
9120
9383
  continue;
9121
9384
  }
9122
9385
  if (!read) continue;
@@ -9131,7 +9394,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
9131
9394
  });
9132
9395
  }
9133
9396
  const anchorId = service.node.id;
9134
- const evidenceFile = toPosix(import_node_path47.default.relative(scanPath, import_node_path47.default.join(service.dir, relFile)));
9397
+ const evidenceFile = toPosix(import_node_path48.default.relative(scanPath, import_node_path48.default.join(service.dir, relFile)));
9135
9398
  const add = (edgeType, kind, name) => {
9136
9399
  if (!name) return;
9137
9400
  const result = emitPlatformResourceEdge(
@@ -9147,10 +9410,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
9147
9410
  nodesAdded += result.nodesAdded;
9148
9411
  edgesAdded += result.edgesAdded;
9149
9412
  };
9150
- add(import_types35.EdgeType.RUNS_ON, "supabase", "supabase");
9151
- for (const fn of Object.keys(config.functions ?? {})) add(import_types35.EdgeType.DEPENDS_ON, "supabase-function", fn);
9152
- if (config.storage) add(import_types35.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
9153
- if (config.auth) add(import_types35.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
9413
+ add(import_types37.EdgeType.RUNS_ON, "supabase", "supabase");
9414
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types37.EdgeType.DEPENDS_ON, "supabase-function", fn);
9415
+ if (config.storage) add(import_types37.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
9416
+ if (config.auth) add(import_types37.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
9154
9417
  }
9155
9418
  return { nodesAdded, edgesAdded };
9156
9419
  }
@@ -9172,17 +9435,17 @@ async function addInfra(graph, scanPath, services) {
9172
9435
  }
9173
9436
 
9174
9437
  // src/extract/index.ts
9175
- var import_node_path49 = __toESM(require("path"), 1);
9438
+ var import_node_path50 = __toESM(require("path"), 1);
9176
9439
 
9177
9440
  // src/extract/retire.ts
9178
9441
  init_cjs_shims();
9179
9442
  var import_node_fs24 = require("fs");
9180
- var import_node_path48 = __toESM(require("path"), 1);
9181
- var import_types36 = require("@neat.is/types");
9443
+ var import_node_path49 = __toESM(require("path"), 1);
9444
+ var import_types38 = require("@neat.is/types");
9182
9445
  function dropOrphanedFileNodes(graph) {
9183
9446
  const orphans = [];
9184
9447
  graph.forEachNode((id, attrs) => {
9185
- if (attrs.type !== import_types36.NodeType.FileNode) return;
9448
+ if (attrs.type !== import_types38.NodeType.FileNode) return;
9186
9449
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
9187
9450
  orphans.push(id);
9188
9451
  }
@@ -9195,14 +9458,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
9195
9458
  const bases = [scanPath, ...serviceDirs];
9196
9459
  graph.forEachEdge((id, attrs) => {
9197
9460
  const edge = attrs;
9198
- if (edge.provenance !== import_types36.Provenance.EXTRACTED) return;
9461
+ if (edge.provenance !== import_types38.Provenance.EXTRACTED) return;
9199
9462
  const evidenceFile = edge.evidence?.file;
9200
9463
  if (!evidenceFile) return;
9201
- if (import_node_path48.default.isAbsolute(evidenceFile)) {
9464
+ if (import_node_path49.default.isAbsolute(evidenceFile)) {
9202
9465
  if (!(0, import_node_fs24.existsSync)(evidenceFile)) toDrop.push(id);
9203
9466
  return;
9204
9467
  }
9205
- const found = bases.some((base) => (0, import_node_fs24.existsSync)(import_node_path48.default.join(base, evidenceFile)));
9468
+ const found = bases.some((base) => (0, import_node_fs24.existsSync)(import_node_path49.default.join(base, evidenceFile)));
9206
9469
  if (!found) toDrop.push(id);
9207
9470
  });
9208
9471
  for (const id of toDrop) graph.dropEdge(id);
@@ -9255,7 +9518,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
9255
9518
  }
9256
9519
  const droppedEntries = drainDroppedExtracted();
9257
9520
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
9258
- const rejectedPath = import_node_path49.default.join(import_node_path49.default.dirname(opts.errorsPath), "rejected.ndjson");
9521
+ const rejectedPath = import_node_path50.default.join(import_node_path50.default.dirname(opts.errorsPath), "rejected.ndjson");
9259
9522
  try {
9260
9523
  await writeRejectedExtracted(droppedEntries, rejectedPath);
9261
9524
  } catch (err) {
@@ -9290,9 +9553,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
9290
9553
  // src/persist.ts
9291
9554
  init_cjs_shims();
9292
9555
  var import_node_fs25 = require("fs");
9293
- var import_node_path50 = __toESM(require("path"), 1);
9294
- var import_types37 = require("@neat.is/types");
9295
- var SCHEMA_VERSION = 5;
9556
+ var import_node_path51 = __toESM(require("path"), 1);
9557
+ var import_types39 = require("@neat.is/types");
9558
+ var SCHEMA_VERSION = 6;
9296
9559
  function migrateV1ToV2(payload) {
9297
9560
  const nodes = payload.graph.nodes;
9298
9561
  if (Array.isArray(nodes)) {
@@ -9310,18 +9573,30 @@ function migrateV3ToV4(payload) {
9310
9573
  function migrateV4ToV5(payload) {
9311
9574
  return { ...payload, schemaVersion: 5 };
9312
9575
  }
9576
+ function migrateV5ToV6(payload) {
9577
+ const nodes = payload.graph.nodes;
9578
+ if (Array.isArray(nodes)) {
9579
+ for (const node of nodes) {
9580
+ const attrs = node.attributes;
9581
+ if (!attrs || attrs.type !== import_types39.NodeType.InfraNode) continue;
9582
+ if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
9583
+ if (!Array.isArray(attrs.columns)) attrs.columns = [];
9584
+ }
9585
+ }
9586
+ return { ...payload, schemaVersion: 6 };
9587
+ }
9313
9588
  function migrateV2ToV3(payload) {
9314
9589
  const edges = payload.graph.edges;
9315
9590
  if (Array.isArray(edges)) {
9316
9591
  for (const edge of edges) {
9317
9592
  const attrs = edge.attributes;
9318
9593
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
9319
- attrs.provenance = import_types37.Provenance.OBSERVED;
9594
+ attrs.provenance = import_types39.Provenance.OBSERVED;
9320
9595
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
9321
9596
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
9322
9597
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
9323
9598
  if (type && source && target) {
9324
- const newId = (0, import_types37.observedEdgeId)(source, target, type);
9599
+ const newId = (0, import_types39.observedEdgeId)(source, target, type);
9325
9600
  attrs.id = newId;
9326
9601
  if (edge.key) edge.key = newId;
9327
9602
  }
@@ -9330,7 +9605,7 @@ function migrateV2ToV3(payload) {
9330
9605
  return { ...payload, schemaVersion: 3 };
9331
9606
  }
9332
9607
  async function ensureDir(filePath) {
9333
- await import_node_fs25.promises.mkdir(import_node_path50.default.dirname(filePath), { recursive: true });
9608
+ await import_node_fs25.promises.mkdir(import_node_path51.default.dirname(filePath), { recursive: true });
9334
9609
  }
9335
9610
  async function saveGraphToDisk(graph, outPath) {
9336
9611
  await ensureDir(outPath);
@@ -9364,6 +9639,9 @@ async function loadGraphFromDisk(graph, outPath) {
9364
9639
  if (payload.schemaVersion === 4) {
9365
9640
  payload = migrateV4ToV5(payload);
9366
9641
  }
9642
+ if (payload.schemaVersion === 5) {
9643
+ payload = migrateV5ToV6(payload);
9644
+ }
9367
9645
  if (payload.schemaVersion !== SCHEMA_VERSION) {
9368
9646
  throw new Error(
9369
9647
  `persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`
@@ -9416,19 +9694,19 @@ function startPersistLoop(graph, outPath, opts = {}) {
9416
9694
  init_cjs_shims();
9417
9695
  var import_fastify2 = __toESM(require("fastify"), 1);
9418
9696
  var import_cors = __toESM(require("@fastify/cors"), 1);
9419
- var import_types56 = require("@neat.is/types");
9697
+ var import_types58 = require("@neat.is/types");
9420
9698
 
9421
9699
  // src/extend/index.ts
9422
9700
  init_cjs_shims();
9423
9701
  var import_node_fs27 = require("fs");
9424
- var import_node_path52 = __toESM(require("path"), 1);
9702
+ var import_node_path53 = __toESM(require("path"), 1);
9425
9703
  var import_node_os2 = __toESM(require("os"), 1);
9426
9704
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
9427
9705
 
9428
9706
  // src/installers/package-manager.ts
9429
9707
  init_cjs_shims();
9430
9708
  var import_node_fs26 = require("fs");
9431
- var import_node_path51 = __toESM(require("path"), 1);
9709
+ var import_node_path52 = __toESM(require("path"), 1);
9432
9710
  var import_node_child_process = require("child_process");
9433
9711
  var LOCKFILE_PRIORITY = [
9434
9712
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -9450,22 +9728,22 @@ async function exists2(p) {
9450
9728
  }
9451
9729
  }
9452
9730
  async function detectPackageManager(serviceDir) {
9453
- let dir = import_node_path51.default.resolve(serviceDir);
9731
+ let dir = import_node_path52.default.resolve(serviceDir);
9454
9732
  const stops = /* @__PURE__ */ new Set();
9455
9733
  for (let i = 0; i < 64; i++) {
9456
9734
  if (stops.has(dir)) break;
9457
9735
  stops.add(dir);
9458
9736
  for (const candidate of LOCKFILE_PRIORITY) {
9459
- const lockPath = import_node_path51.default.join(dir, candidate.lockfile);
9737
+ const lockPath = import_node_path52.default.join(dir, candidate.lockfile);
9460
9738
  if (await exists2(lockPath)) {
9461
9739
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
9462
9740
  }
9463
9741
  }
9464
- const parent = import_node_path51.default.dirname(dir);
9742
+ const parent = import_node_path52.default.dirname(dir);
9465
9743
  if (parent === dir) break;
9466
9744
  dir = parent;
9467
9745
  }
9468
- return { pm: "npm", cwd: import_node_path51.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
9746
+ return { pm: "npm", cwd: import_node_path52.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
9469
9747
  }
9470
9748
  async function runPackageManagerInstall(cmd) {
9471
9749
  return new Promise((resolve) => {
@@ -9514,7 +9792,7 @@ async function fileExists2(p) {
9514
9792
  }
9515
9793
  }
9516
9794
  async function readPackageJson(scanPath) {
9517
- const pkgPath = import_node_path52.default.join(scanPath, "package.json");
9795
+ const pkgPath = import_node_path53.default.join(scanPath, "package.json");
9518
9796
  const raw = await import_node_fs27.promises.readFile(pkgPath, "utf8");
9519
9797
  return JSON.parse(raw);
9520
9798
  }
@@ -9533,11 +9811,11 @@ async function findHookFiles(scanPath) {
9533
9811
  for (const entry of entries) {
9534
9812
  if (entry.isDirectory()) {
9535
9813
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
9536
- await walk6(import_node_path52.default.join(dir, entry.name));
9814
+ await walk6(import_node_path53.default.join(dir, entry.name));
9537
9815
  } else if (entry.isFile()) {
9538
9816
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
9539
- const rel = import_node_path52.default.relative(scanPath, import_node_path52.default.join(dir, entry.name));
9540
- found.push(rel.split(import_node_path52.default.sep).join("/"));
9817
+ const rel = import_node_path53.default.relative(scanPath, import_node_path53.default.join(dir, entry.name));
9818
+ found.push(rel.split(import_node_path53.default.sep).join("/"));
9541
9819
  }
9542
9820
  }
9543
9821
  }
@@ -9548,7 +9826,7 @@ async function findHookFiles(scanPath) {
9548
9826
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
9549
9827
  let fallback = null;
9550
9828
  for (const file of hookFiles) {
9551
- const content = await import_node_fs27.promises.readFile(import_node_path52.default.join(scanPath, file), "utf8");
9829
+ const content = await import_node_fs27.promises.readFile(import_node_path53.default.join(scanPath, file), "utf8");
9552
9830
  const patched = splicedContent(content, snippet2);
9553
9831
  if (patched !== null) return { file, content, patched };
9554
9832
  if (fallback === null) fallback = { file, content };
@@ -9556,11 +9834,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
9556
9834
  return { file: fallback.file, content: fallback.content, patched: null };
9557
9835
  }
9558
9836
  function extendLogPath() {
9559
- return process.env.NEAT_EXTEND_LOG ?? import_node_path52.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
9837
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path53.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
9560
9838
  }
9561
9839
  async function appendExtendLog(entry) {
9562
9840
  const logPath = extendLogPath();
9563
- await import_node_fs27.promises.mkdir(import_node_path52.default.dirname(logPath), { recursive: true });
9841
+ await import_node_fs27.promises.mkdir(import_node_path53.default.dirname(logPath), { recursive: true });
9564
9842
  await import_node_fs27.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
9565
9843
  }
9566
9844
  function splicedContent(fileContent, snippet2) {
@@ -9619,7 +9897,7 @@ function lookupInstrumentation(library, installedVersion) {
9619
9897
  }
9620
9898
  async function describeProjectInstrumentation(ctx) {
9621
9899
  const hookFiles = await findHookFiles(ctx.scanPath);
9622
- const envNeat = await fileExists2(import_node_path52.default.join(ctx.scanPath, ".env.neat"));
9900
+ const envNeat = await fileExists2(import_node_path53.default.join(ctx.scanPath, ".env.neat"));
9623
9901
  const registryInstrPackages = new Set(
9624
9902
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
9625
9903
  );
@@ -9641,7 +9919,7 @@ async function applyExtension(ctx, args, options) {
9641
9919
  );
9642
9920
  }
9643
9921
  for (const file of hookFiles) {
9644
- const content = await import_node_fs27.promises.readFile(import_node_path52.default.join(ctx.scanPath, file), "utf8");
9922
+ const content = await import_node_fs27.promises.readFile(import_node_path53.default.join(ctx.scanPath, file), "utf8");
9645
9923
  if (content.includes(args.registration_snippet)) {
9646
9924
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
9647
9925
  }
@@ -9653,10 +9931,10 @@ async function applyExtension(ctx, args, options) {
9653
9931
  );
9654
9932
  }
9655
9933
  const primaryFile = primary.file;
9656
- const primaryPath = import_node_path52.default.join(ctx.scanPath, primaryFile);
9934
+ const primaryPath = import_node_path53.default.join(ctx.scanPath, primaryFile);
9657
9935
  const filesTouched = [];
9658
9936
  const depsAdded = [];
9659
- const pkgPath = import_node_path52.default.join(ctx.scanPath, "package.json");
9937
+ const pkgPath = import_node_path53.default.join(ctx.scanPath, "package.json");
9660
9938
  const pkg = await readPackageJson(ctx.scanPath);
9661
9939
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
9662
9940
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -9695,7 +9973,7 @@ async function dryRunExtension(ctx, args) {
9695
9973
  };
9696
9974
  }
9697
9975
  for (const file of hookFiles) {
9698
- const content = await import_node_fs27.promises.readFile(import_node_path52.default.join(ctx.scanPath, file), "utf8");
9976
+ const content = await import_node_fs27.promises.readFile(import_node_path53.default.join(ctx.scanPath, file), "utf8");
9699
9977
  if (content.includes(args.registration_snippet)) {
9700
9978
  return {
9701
9979
  library: args.library,
@@ -9736,7 +10014,7 @@ async function rollbackExtension(ctx, args) {
9736
10014
  if (!match) {
9737
10015
  return { undone: false, message: "no apply found for library" };
9738
10016
  }
9739
- const pkgPath = import_node_path52.default.join(ctx.scanPath, "package.json");
10017
+ const pkgPath = import_node_path53.default.join(ctx.scanPath, "package.json");
9740
10018
  if (await fileExists2(pkgPath)) {
9741
10019
  const pkg = await readPackageJson(ctx.scanPath);
9742
10020
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -9747,7 +10025,7 @@ async function rollbackExtension(ctx, args) {
9747
10025
  }
9748
10026
  const hookFiles = await findHookFiles(ctx.scanPath);
9749
10027
  for (const file of hookFiles) {
9750
- const filePath = import_node_path52.default.join(ctx.scanPath, file);
10028
+ const filePath = import_node_path53.default.join(ctx.scanPath, file);
9751
10029
  const content = await import_node_fs27.promises.readFile(filePath, "utf8");
9752
10030
  if (content.includes(match.registration_snippet)) {
9753
10031
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -9763,39 +10041,39 @@ async function rollbackExtension(ctx, args) {
9763
10041
 
9764
10042
  // src/divergences.ts
9765
10043
  init_cjs_shims();
9766
- var import_types38 = require("@neat.is/types");
10044
+ var import_types40 = require("@neat.is/types");
9767
10045
  function bucketKey(source, target, type) {
9768
10046
  return `${type}|${source}|${target}`;
9769
10047
  }
9770
10048
  function bucketSourceFor(graph, edge) {
9771
- if (edge.type !== import_types38.EdgeType.CONNECTS_TO) return edge.source;
9772
- const parsed = (0, import_types38.parseFileId)(edge.source);
10049
+ if (edge.type !== import_types40.EdgeType.CONNECTS_TO) return edge.source;
10050
+ const parsed = (0, import_types40.parseFileId)(edge.source);
9773
10051
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
9774
10052
  const target = graph.getNodeAttributes(edge.target);
9775
- if (target.type !== import_types38.NodeType.DatabaseNode) return edge.source;
9776
- return (0, import_types38.serviceId)(parsed.service);
10053
+ if (target.type !== import_types40.NodeType.DatabaseNode) return edge.source;
10054
+ return (0, import_types40.serviceId)(parsed.service);
9777
10055
  }
9778
10056
  function bucketEdges(graph) {
9779
10057
  const buckets2 = /* @__PURE__ */ new Map();
9780
10058
  graph.forEachEdge((id, attrs) => {
9781
10059
  const e = attrs;
9782
- const parsed = (0, import_types38.parseEdgeId)(id);
10060
+ const parsed = (0, import_types40.parseEdgeId)(id);
9783
10061
  const provenance = parsed?.provenance ?? e.provenance;
9784
10062
  const source = bucketSourceFor(graph, e);
9785
10063
  const key = bucketKey(source, e.target, e.type);
9786
10064
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
9787
10065
  switch (provenance) {
9788
- case import_types38.Provenance.EXTRACTED:
10066
+ case import_types40.Provenance.EXTRACTED:
9789
10067
  cur.extracted = e;
9790
10068
  break;
9791
- case import_types38.Provenance.OBSERVED:
10069
+ case import_types40.Provenance.OBSERVED:
9792
10070
  cur.observed = e;
9793
10071
  break;
9794
- case import_types38.Provenance.INFERRED:
10072
+ case import_types40.Provenance.INFERRED:
9795
10073
  cur.inferred = e;
9796
10074
  break;
9797
10075
  default:
9798
- if (e.provenance === import_types38.Provenance.STALE) cur.stale = e;
10076
+ if (e.provenance === import_types40.Provenance.STALE) cur.stale = e;
9799
10077
  }
9800
10078
  buckets2.set(key, cur);
9801
10079
  });
@@ -9804,17 +10082,17 @@ function bucketEdges(graph) {
9804
10082
  function nodeIsFrontier(graph, nodeId) {
9805
10083
  if (!graph.hasNode(nodeId)) return false;
9806
10084
  const attrs = graph.getNodeAttributes(nodeId);
9807
- return attrs.type === import_types38.NodeType.FrontierNode;
10085
+ return attrs.type === import_types40.NodeType.FrontierNode;
9808
10086
  }
9809
10087
  function nodeIsWebsocketChannel(graph, nodeId) {
9810
10088
  if (!graph.hasNode(nodeId)) return false;
9811
10089
  const attrs = graph.getNodeAttributes(nodeId);
9812
- return attrs.type === import_types38.NodeType.WebSocketChannelNode;
10090
+ return attrs.type === import_types40.NodeType.WebSocketChannelNode;
9813
10091
  }
9814
10092
  function nodeIsSymbol(graph, nodeId) {
9815
10093
  if (!graph.hasNode(nodeId)) return false;
9816
10094
  const attrs = graph.getNodeAttributes(nodeId);
9817
- return attrs.type === import_types38.NodeType.SymbolNode;
10095
+ return attrs.type === import_types40.NodeType.SymbolNode;
9818
10096
  }
9819
10097
  function clampConfidence(n) {
9820
10098
  if (!Number.isFinite(n)) return 0;
@@ -9834,14 +10112,14 @@ function gradedConfidence(edge) {
9834
10112
  return clampConfidence(confidenceForEdge(edge));
9835
10113
  }
9836
10114
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
9837
- import_types38.EdgeType.CALLS,
9838
- import_types38.EdgeType.CONNECTS_TO,
9839
- import_types38.EdgeType.PUBLISHES_TO,
9840
- import_types38.EdgeType.CONSUMES_FROM
10115
+ import_types40.EdgeType.CALLS,
10116
+ import_types40.EdgeType.CONNECTS_TO,
10117
+ import_types40.EdgeType.PUBLISHES_TO,
10118
+ import_types40.EdgeType.CONSUMES_FROM
9841
10119
  ]);
9842
10120
  function detectMissingDivergences(graph, bucket) {
9843
10121
  const out = [];
9844
- if (bucket.type === import_types38.EdgeType.CONTAINS) return out;
10122
+ if (bucket.type === import_types40.EdgeType.CONTAINS) return out;
9845
10123
  if (nodeIsSymbol(graph, bucket.source) || nodeIsSymbol(graph, bucket.target)) return out;
9846
10124
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
9847
10125
  if (!nodeIsFrontier(graph, bucket.target)) {
@@ -9883,7 +10161,7 @@ function declaredHostFor(svc) {
9883
10161
  function hasExtractedConfiguredBy(graph, svcId) {
9884
10162
  for (const edgeId of graph.outboundEdges(svcId)) {
9885
10163
  const e = graph.getEdgeAttributes(edgeId);
9886
- if (e.type === import_types38.EdgeType.CONFIGURED_BY && e.provenance === import_types38.Provenance.EXTRACTED) {
10164
+ if (e.type === import_types40.EdgeType.CONFIGURED_BY && e.provenance === import_types40.Provenance.EXTRACTED) {
9887
10165
  return true;
9888
10166
  }
9889
10167
  }
@@ -9896,10 +10174,10 @@ function detectHostMismatch(graph, svcId, svc) {
9896
10174
  const out = [];
9897
10175
  for (const edgeId of graph.outboundEdges(svcId)) {
9898
10176
  const edge = graph.getEdgeAttributes(edgeId);
9899
- if (edge.type !== import_types38.EdgeType.CONNECTS_TO) continue;
9900
- if (edge.provenance !== import_types38.Provenance.OBSERVED) continue;
10177
+ if (edge.type !== import_types40.EdgeType.CONNECTS_TO) continue;
10178
+ if (edge.provenance !== import_types40.Provenance.OBSERVED) continue;
9901
10179
  const target = graph.getNodeAttributes(edge.target);
9902
- if (target.type !== import_types38.NodeType.DatabaseNode) continue;
10180
+ if (target.type !== import_types40.NodeType.DatabaseNode) continue;
9903
10181
  const observedHost = target.host?.trim();
9904
10182
  if (!observedHost) continue;
9905
10183
  if (observedHost === declaredHost) continue;
@@ -9921,10 +10199,10 @@ function detectCompatDivergences(graph, svcId, svc) {
9921
10199
  const deps = svc.dependencies ?? {};
9922
10200
  for (const edgeId of graph.outboundEdges(svcId)) {
9923
10201
  const edge = graph.getEdgeAttributes(edgeId);
9924
- if (edge.type !== import_types38.EdgeType.CONNECTS_TO) continue;
9925
- if (edge.provenance !== import_types38.Provenance.OBSERVED) continue;
10202
+ if (edge.type !== import_types40.EdgeType.CONNECTS_TO) continue;
10203
+ if (edge.provenance !== import_types40.Provenance.OBSERVED) continue;
9926
10204
  const target = graph.getNodeAttributes(edge.target);
9927
- if (target.type !== import_types38.NodeType.DatabaseNode) continue;
10205
+ if (target.type !== import_types40.NodeType.DatabaseNode) continue;
9928
10206
  for (const pair of compatPairs()) {
9929
10207
  if (pair.engine !== target.engine) continue;
9930
10208
  const declared = deps[pair.driver];
@@ -9974,6 +10252,44 @@ function detectCompatDivergences(graph, svcId, svc) {
9974
10252
  }
9975
10253
  return out;
9976
10254
  }
10255
+ 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.";
10256
+ 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.";
10257
+ function detectColumnDrift(node) {
10258
+ const columns = node.columns;
10259
+ if (!columns || columns.length === 0) return [];
10260
+ const anyDeclared = columns.some(columnIsDeclared);
10261
+ const anyObserved = columns.some(columnIsObserved);
10262
+ if (!anyDeclared || !anyObserved) return [];
10263
+ const out = [];
10264
+ for (const col of columns) {
10265
+ const declared = columnIsDeclared(col);
10266
+ const observed = columnIsObserved(col);
10267
+ if (declared && !observed) {
10268
+ out.push({
10269
+ type: "missing-observed",
10270
+ source: node.id,
10271
+ target: node.id,
10272
+ table: node.id,
10273
+ column: col.name,
10274
+ confidence: clampConfidence(col.confidence),
10275
+ reason: `Schema declares column ${node.name}.${col.name} but no production statement has touched it.`,
10276
+ recommendation: RECOMMENDATION_COLUMN_MISSING_OBSERVED
10277
+ });
10278
+ } else if (observed && !declared) {
10279
+ out.push({
10280
+ type: "missing-extracted",
10281
+ source: node.id,
10282
+ target: node.id,
10283
+ table: node.id,
10284
+ column: col.name,
10285
+ confidence: clampConfidence(col.confidence),
10286
+ reason: `Production touched column ${node.name}.${col.name} but the schema does not declare it.`,
10287
+ recommendation: RECOMMENDATION_COLUMN_MISSING_EXTRACTED
10288
+ });
10289
+ }
10290
+ }
10291
+ return out;
10292
+ }
9977
10293
  function involvesNode(d, nodeId) {
9978
10294
  return d.source === nodeId || d.target === nodeId;
9979
10295
  }
@@ -9983,7 +10299,7 @@ function suppressHostMismatchHalves(all) {
9983
10299
  for (const d of all) {
9984
10300
  if (d.type !== "host-mismatch") continue;
9985
10301
  observedHalf.add(`${d.source}->${d.target}`);
9986
- declaredHalf.add((0, import_types38.databaseId)(d.extractedHost));
10302
+ declaredHalf.add((0, import_types40.databaseId)(d.extractedHost));
9987
10303
  }
9988
10304
  if (observedHalf.size === 0) return all;
9989
10305
  return all.filter((d) => {
@@ -10002,10 +10318,15 @@ function computeDivergences(graph, opts = {}) {
10002
10318
  }
10003
10319
  graph.forEachNode((nodeId, attrs) => {
10004
10320
  const n = attrs;
10005
- if (n.type !== import_types38.NodeType.ServiceNode) return;
10006
- const svc = n;
10007
- for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
10008
- for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
10321
+ if (n.type === import_types40.NodeType.ServiceNode) {
10322
+ const svc = n;
10323
+ for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
10324
+ for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
10325
+ return;
10326
+ }
10327
+ if (n.type === import_types40.NodeType.InfraNode && n.kind === "sql-table") {
10328
+ for (const d of detectColumnDrift(n)) all.push(d);
10329
+ }
10009
10330
  });
10010
10331
  const reconciled = suppressHostMismatchHalves(all);
10011
10332
  let filtered = reconciled;
@@ -10034,9 +10355,12 @@ function computeDivergences(graph, opts = {}) {
10034
10355
  if (lead !== 0) return lead;
10035
10356
  if (a.type !== b.type) return a.type.localeCompare(b.type);
10036
10357
  if (a.source !== b.source) return a.source.localeCompare(b.source);
10037
- return a.target.localeCompare(b.target);
10358
+ if (a.target !== b.target) return a.target.localeCompare(b.target);
10359
+ const ac = "column" in a && a.column ? a.column : "";
10360
+ const bc = "column" in b && b.column ? b.column : "";
10361
+ return ac.localeCompare(bc);
10038
10362
  });
10039
- return import_types38.DivergenceResultSchema.parse({
10363
+ return import_types40.DivergenceResultSchema.parse({
10040
10364
  divergences: filtered,
10041
10365
  totalAffected: filtered.length,
10042
10366
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -10167,23 +10491,23 @@ function canonicalJson(value) {
10167
10491
 
10168
10492
  // src/projects.ts
10169
10493
  init_cjs_shims();
10170
- var import_node_path53 = __toESM(require("path"), 1);
10494
+ var import_node_path54 = __toESM(require("path"), 1);
10171
10495
  function pathsForProject(project, baseDir) {
10172
10496
  if (project === DEFAULT_PROJECT) {
10173
10497
  return {
10174
- snapshotPath: import_node_path53.default.join(baseDir, "graph.json"),
10175
- errorsPath: import_node_path53.default.join(baseDir, "errors.ndjson"),
10176
- staleEventsPath: import_node_path53.default.join(baseDir, "stale-events.ndjson"),
10177
- embeddingsCachePath: import_node_path53.default.join(baseDir, "embeddings.json"),
10178
- policyViolationsPath: import_node_path53.default.join(baseDir, "policy-violations.ndjson")
10498
+ snapshotPath: import_node_path54.default.join(baseDir, "graph.json"),
10499
+ errorsPath: import_node_path54.default.join(baseDir, "errors.ndjson"),
10500
+ staleEventsPath: import_node_path54.default.join(baseDir, "stale-events.ndjson"),
10501
+ embeddingsCachePath: import_node_path54.default.join(baseDir, "embeddings.json"),
10502
+ policyViolationsPath: import_node_path54.default.join(baseDir, "policy-violations.ndjson")
10179
10503
  };
10180
10504
  }
10181
10505
  return {
10182
- snapshotPath: import_node_path53.default.join(baseDir, `${project}.json`),
10183
- errorsPath: import_node_path53.default.join(baseDir, `errors.${project}.ndjson`),
10184
- staleEventsPath: import_node_path53.default.join(baseDir, `stale-events.${project}.ndjson`),
10185
- embeddingsCachePath: import_node_path53.default.join(baseDir, `embeddings.${project}.json`),
10186
- policyViolationsPath: import_node_path53.default.join(baseDir, `policy-violations.${project}.ndjson`)
10506
+ snapshotPath: import_node_path54.default.join(baseDir, `${project}.json`),
10507
+ errorsPath: import_node_path54.default.join(baseDir, `errors.${project}.ndjson`),
10508
+ staleEventsPath: import_node_path54.default.join(baseDir, `stale-events.${project}.ndjson`),
10509
+ embeddingsCachePath: import_node_path54.default.join(baseDir, `embeddings.${project}.json`),
10510
+ policyViolationsPath: import_node_path54.default.join(baseDir, `policy-violations.${project}.ndjson`)
10187
10511
  };
10188
10512
  }
10189
10513
  var Projects = class {
@@ -10221,26 +10545,26 @@ var Projects = class {
10221
10545
  init_cjs_shims();
10222
10546
  var import_node_fs29 = require("fs");
10223
10547
  var import_node_os3 = __toESM(require("os"), 1);
10224
- var import_node_path54 = __toESM(require("path"), 1);
10225
- var import_types39 = require("@neat.is/types");
10548
+ var import_node_path55 = __toESM(require("path"), 1);
10549
+ var import_types41 = require("@neat.is/types");
10226
10550
  var LOCK_TIMEOUT_MS = 5e3;
10227
10551
  var LOCK_RETRY_MS = 50;
10228
10552
  function neatHome() {
10229
10553
  const override = process.env.NEAT_HOME;
10230
- if (override && override.length > 0) return import_node_path54.default.resolve(override);
10231
- return import_node_path54.default.join(import_node_os3.default.homedir(), ".neat");
10554
+ if (override && override.length > 0) return import_node_path55.default.resolve(override);
10555
+ return import_node_path55.default.join(import_node_os3.default.homedir(), ".neat");
10232
10556
  }
10233
10557
  function registryPath() {
10234
- return import_node_path54.default.join(neatHome(), "projects.json");
10558
+ return import_node_path55.default.join(neatHome(), "projects.json");
10235
10559
  }
10236
10560
  function registryLockPath() {
10237
- return import_node_path54.default.join(neatHome(), "projects.json.lock");
10561
+ return import_node_path55.default.join(neatHome(), "projects.json.lock");
10238
10562
  }
10239
10563
  function daemonPidPath() {
10240
- return import_node_path54.default.join(neatHome(), "neatd.pid");
10564
+ return import_node_path55.default.join(neatHome(), "neatd.pid");
10241
10565
  }
10242
10566
  function daemonsDir() {
10243
- return import_node_path54.default.join(neatHome(), "daemons");
10567
+ return import_node_path55.default.join(neatHome(), "daemons");
10244
10568
  }
10245
10569
  function isFiniteInt(v) {
10246
10570
  return typeof v === "number" && Number.isFinite(v);
@@ -10281,7 +10605,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
10281
10605
  const out = [];
10282
10606
  for (const name of names) {
10283
10607
  if (!name.endsWith(".json")) continue;
10284
- const file = import_node_path54.default.join(dir, name);
10608
+ const file = import_node_path55.default.join(dir, name);
10285
10609
  let raw;
10286
10610
  try {
10287
10611
  raw = await import_node_fs29.promises.readFile(file, "utf8");
@@ -10358,7 +10682,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
10358
10682
  }
10359
10683
  }
10360
10684
  async function normalizeProjectPath(input) {
10361
- const resolved = import_node_path54.default.resolve(input);
10685
+ const resolved = import_node_path55.default.resolve(input);
10362
10686
  try {
10363
10687
  return await import_node_fs29.promises.realpath(resolved);
10364
10688
  } catch {
@@ -10366,7 +10690,7 @@ async function normalizeProjectPath(input) {
10366
10690
  }
10367
10691
  }
10368
10692
  async function writeAtomically(target, contents) {
10369
- await import_node_fs29.promises.mkdir(import_node_path54.default.dirname(target), { recursive: true });
10693
+ await import_node_fs29.promises.mkdir(import_node_path55.default.dirname(target), { recursive: true });
10370
10694
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
10371
10695
  const fd = await import_node_fs29.promises.open(tmp, "w");
10372
10696
  try {
@@ -10379,7 +10703,7 @@ async function writeAtomically(target, contents) {
10379
10703
  }
10380
10704
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
10381
10705
  const deadline = Date.now() + timeoutMs;
10382
- await import_node_fs29.promises.mkdir(import_node_path54.default.dirname(lockPath), { recursive: true });
10706
+ await import_node_fs29.promises.mkdir(import_node_path55.default.dirname(lockPath), { recursive: true });
10383
10707
  let probedHolder = false;
10384
10708
  while (true) {
10385
10709
  try {
@@ -10432,10 +10756,10 @@ async function readRegistry() {
10432
10756
  throw err;
10433
10757
  }
10434
10758
  const parsed = JSON.parse(raw);
10435
- return import_types39.RegistryFileSchema.parse(parsed);
10759
+ return import_types41.RegistryFileSchema.parse(parsed);
10436
10760
  }
10437
10761
  async function writeRegistry(reg) {
10438
- const validated = import_types39.RegistryFileSchema.parse(reg);
10762
+ const validated = import_types41.RegistryFileSchema.parse(reg);
10439
10763
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
10440
10764
  }
10441
10765
  var ProjectNameCollisionError = class extends Error {
@@ -10630,7 +10954,7 @@ init_auth();
10630
10954
  // src/connectors-config.ts
10631
10955
  init_cjs_shims();
10632
10956
  var import_node_os4 = __toESM(require("os"), 1);
10633
- var import_node_path55 = __toESM(require("path"), 1);
10957
+ var import_node_path56 = __toESM(require("path"), 1);
10634
10958
  var import_node_fs30 = require("fs");
10635
10959
  var CONNECTORS_CONFIG_VERSION = 1;
10636
10960
  var EnvRefUnsetError = class extends Error {
@@ -10645,11 +10969,11 @@ var EnvRefUnsetError = class extends Error {
10645
10969
  };
10646
10970
  function neatHome2() {
10647
10971
  const override = process.env.NEAT_HOME;
10648
- if (override && override.length > 0) return import_node_path55.default.resolve(override);
10649
- return import_node_path55.default.join(import_node_os4.default.homedir(), ".neat");
10972
+ if (override && override.length > 0) return import_node_path56.default.resolve(override);
10973
+ return import_node_path56.default.join(import_node_os4.default.homedir(), ".neat");
10650
10974
  }
10651
10975
  function connectorsConfigPath(home = neatHome2()) {
10652
- return import_node_path55.default.join(home, "connectors.json");
10976
+ return import_node_path56.default.join(home, "connectors.json");
10653
10977
  }
10654
10978
  var MODE_MASK_LOOSER_THAN_0600 = 63;
10655
10979
  async function warnIfModeLooserThan0600(file) {
@@ -10836,15 +11160,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
10836
11160
 
10837
11161
  // src/connectors/index.ts
10838
11162
  init_cjs_shims();
10839
- var import_types40 = require("@neat.is/types");
11163
+ var import_types42 = require("@neat.is/types");
10840
11164
  var NO_ENV = "unknown";
10841
11165
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
10842
11166
  if (!graph.hasNode(targetNodeId)) return void 0;
10843
11167
  const sites = [];
10844
11168
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
10845
11169
  const edge = graph.getEdgeAttributes(edgeId);
10846
- if (edge.provenance !== import_types40.Provenance.EXTRACTED) continue;
10847
- const parsed = (0, import_types40.parseFileId)(edge.source);
11170
+ if (edge.provenance !== import_types42.Provenance.EXTRACTED) continue;
11171
+ const parsed = (0, import_types42.parseFileId)(edge.source);
10848
11172
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
10849
11173
  const site = { relPath: edge.evidence.file };
10850
11174
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -10855,7 +11179,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
10855
11179
  function routeCallSiteFor(graph, targetNodeId) {
10856
11180
  if (!graph.hasNode(targetNodeId)) return void 0;
10857
11181
  const attrs = graph.getNodeAttributes(targetNodeId);
10858
- if (attrs.type !== import_types40.NodeType.RouteNode || !attrs.path) return void 0;
11182
+ if (attrs.type !== import_types42.NodeType.RouteNode || !attrs.path) return void 0;
10859
11183
  const site = { relPath: attrs.path };
10860
11184
  if (attrs.line !== void 0) site.line = attrs.line;
10861
11185
  return site;
@@ -10875,6 +11199,7 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
10875
11199
  const { kind, name, provider } = resolved.ensureInfraNode;
10876
11200
  ensureInfraNode(graph, kind, name, provider);
10877
11201
  }
11202
+ mergeObservedColumns(graph, resolved.targetNodeId, signal.columns);
10878
11203
  const serviceNodeId = ensureServiceNode(graph, resolved.serviceName, NO_ENV);
10879
11204
  const callSite = signal.callSite ? { relPath: signal.callSite.file, line: signal.callSite.line } : routeCallSiteFor(graph, resolved.targetNodeId) ?? staticCallSiteFor(graph, resolved.serviceName, resolved.targetNodeId);
10880
11205
  const sourceId = callSite ? ensureObservedFileNode(graph, resolved.serviceName, serviceNodeId, callSite) : serviceNodeId;
@@ -11305,6 +11630,7 @@ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl
11305
11630
 
11306
11631
  // src/connectors/supabase/map.ts
11307
11632
  init_cjs_shims();
11633
+ init_otel();
11308
11634
 
11309
11635
  // src/connectors/supabase/types.ts
11310
11636
  init_cjs_shims();
@@ -11330,10 +11656,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
11330
11656
  // src/connectors/supabase/map.ts
11331
11657
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
11332
11658
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
11333
- function targetFromRestPath(path58) {
11334
- const rpcMatch = REST_RPC_PATH_RE.exec(path58);
11659
+ function targetFromRestPath(path59) {
11660
+ const rpcMatch = REST_RPC_PATH_RE.exec(path59);
11335
11661
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
11336
- const tableMatch = REST_TABLE_PATH_RE.exec(path58);
11662
+ const tableMatch = REST_TABLE_PATH_RE.exec(path59);
11337
11663
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
11338
11664
  return null;
11339
11665
  }
@@ -11397,12 +11723,14 @@ function diffPgStatStatementsToSignals(rows, previous, nowIso2) {
11397
11723
  if (delta <= 0) continue;
11398
11724
  const table = tableNameFromQueryText(row.query);
11399
11725
  if (!table) continue;
11726
+ const columns = columnsFromSqlStatement(row.query);
11400
11727
  signals.push({
11401
11728
  targetKind: SUPABASE_TABLE_TARGET_KIND,
11402
11729
  targetName: table,
11403
11730
  callCount: delta,
11404
11731
  errorCount: 0,
11405
- lastObservedIso: nowIso2
11732
+ lastObservedIso: nowIso2,
11733
+ ...columns.length > 0 ? { columns } : {}
11406
11734
  });
11407
11735
  }
11408
11736
  for (const queryid of [...previous.keys()]) {
@@ -11442,23 +11770,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
11442
11770
 
11443
11771
  // src/connectors/supabase/resolve.ts
11444
11772
  init_cjs_shims();
11445
- var import_types42 = require("@neat.is/types");
11773
+ var import_types44 = require("@neat.is/types");
11446
11774
  function createSupabaseResolveTarget(graph, config) {
11447
11775
  return (signal, _ctx) => {
11448
11776
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
11449
11777
  return null;
11450
11778
  }
11451
- const subResourceId = (0, import_types42.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
11779
+ const subResourceId = (0, import_types44.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
11452
11780
  if (graph.hasNode(subResourceId)) {
11453
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types42.EdgeType.CALLS };
11781
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types44.EdgeType.CALLS };
11454
11782
  }
11455
- const bareResourceId = (0, import_types42.infraId)(signal.targetKind, signal.targetName);
11783
+ const bareResourceId = (0, import_types44.infraId)(signal.targetKind, signal.targetName);
11456
11784
  if (graph.hasNode(bareResourceId)) {
11457
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types42.EdgeType.CALLS };
11785
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types44.EdgeType.CALLS };
11458
11786
  }
11459
- const projectLevelId = (0, import_types42.infraId)("supabase", config.nodeRef);
11787
+ const projectLevelId = (0, import_types44.infraId)("supabase", config.nodeRef);
11460
11788
  if (graph.hasNode(projectLevelId)) {
11461
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types42.EdgeType.CALLS };
11789
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types44.EdgeType.CALLS };
11462
11790
  }
11463
11791
  return null;
11464
11792
  };
@@ -11551,7 +11879,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
11551
11879
 
11552
11880
  // src/connectors/railway/index.ts
11553
11881
  init_cjs_shims();
11554
- var import_types46 = require("@neat.is/types");
11882
+ var import_types48 = require("@neat.is/types");
11555
11883
 
11556
11884
  // src/connectors/railway/client.ts
11557
11885
  init_cjs_shims();
@@ -11702,7 +12030,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
11702
12030
  const out = [];
11703
12031
  graph.forEachNode((_id, attrs) => {
11704
12032
  const node = attrs;
11705
- if (node.type !== import_types46.NodeType.RouteNode) return;
12033
+ if (node.type !== import_types48.NodeType.RouteNode) return;
11706
12034
  const route = attrs;
11707
12035
  if (route.service !== serviceName) return;
11708
12036
  out.push({
@@ -11806,12 +12134,12 @@ function createRailwayResolveTarget(config) {
11806
12134
  const serviceName = config.serviceNameById[config.serviceId];
11807
12135
  if (!serviceName) return null;
11808
12136
  if (signal.targetKind === ROUTE_TARGET_KIND) {
11809
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types46.EdgeType.CALLS };
12137
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types48.EdgeType.CALLS };
11810
12138
  }
11811
12139
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
11812
12140
  const peerName = config.serviceNameById[signal.targetName];
11813
12141
  if (!peerName) return null;
11814
- return { targetNodeId: (0, import_types46.serviceId)(peerName), serviceName, edgeType: import_types46.EdgeType.CONNECTS_TO };
12142
+ return { targetNodeId: (0, import_types48.serviceId)(peerName), serviceName, edgeType: import_types48.EdgeType.CONNECTS_TO };
11815
12143
  }
11816
12144
  return null;
11817
12145
  };
@@ -11935,9 +12263,9 @@ function parseFirebaseTargetName(targetName) {
11935
12263
  const secondSep = rest.indexOf(FIELD_SEP);
11936
12264
  if (secondSep === -1) return null;
11937
12265
  const method = rest.slice(0, secondSep);
11938
- const path58 = rest.slice(secondSep + 1);
11939
- if (!resourceName || !method || !path58) return null;
11940
- return { resourceName, method, path: path58 };
12266
+ const path59 = rest.slice(secondSep + 1);
12267
+ if (!resourceName || !method || !path59) return null;
12268
+ return { resourceName, method, path: path59 };
11941
12269
  }
11942
12270
  function resourceNameFor(type, labels) {
11943
12271
  if (!labels) return null;
@@ -11975,14 +12303,14 @@ function mapLogEntryToSignal(entry) {
11975
12303
  if (!req) return null;
11976
12304
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
11977
12305
  const method = req.requestMethod.toUpperCase();
11978
- const path58 = pathFromRequestUrl(req.requestUrl);
11979
- if (path58 === null) return null;
12306
+ const path59 = pathFromRequestUrl(req.requestUrl);
12307
+ if (path59 === null) return null;
11980
12308
  const timestamp = entry.timestamp;
11981
12309
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
11982
12310
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
11983
12311
  return {
11984
12312
  targetKind: resourceType,
11985
- targetName: packFirebaseTargetName({ resourceName, method, path: path58 }),
12313
+ targetName: packFirebaseTargetName({ resourceName, method, path: path59 }),
11986
12314
  callCount: 1,
11987
12315
  errorCount: isError ? 1 : 0,
11988
12316
  lastObservedIso: timestamp
@@ -11999,7 +12327,7 @@ function mapLogEntriesToSignals(entries) {
11999
12327
 
12000
12328
  // src/connectors/firebase/resolve.ts
12001
12329
  init_cjs_shims();
12002
- var import_types47 = require("@neat.is/types");
12330
+ var import_types49 = require("@neat.is/types");
12003
12331
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
12004
12332
  switch (resourceType) {
12005
12333
  case "cloud_function":
@@ -12014,7 +12342,7 @@ function routeEntriesFor(graph, serviceName) {
12014
12342
  const entries = [];
12015
12343
  graph.forEachNode((_id, attrs) => {
12016
12344
  const node = attrs;
12017
- if (node.type !== import_types47.NodeType.RouteNode) return;
12345
+ if (node.type !== import_types49.NodeType.RouteNode) return;
12018
12346
  const route = attrs;
12019
12347
  if (route.service !== serviceName) return;
12020
12348
  entries.push({
@@ -12046,7 +12374,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
12046
12374
  return {
12047
12375
  targetNodeId: match.routeNodeId,
12048
12376
  serviceName,
12049
- edgeType: import_types47.EdgeType.CALLS
12377
+ edgeType: import_types49.EdgeType.CALLS
12050
12378
  };
12051
12379
  };
12052
12380
  }
@@ -12073,7 +12401,7 @@ init_cjs_shims();
12073
12401
 
12074
12402
  // src/connectors/cloudflare/connector.ts
12075
12403
  init_cjs_shims();
12076
- var import_types49 = require("@neat.is/types");
12404
+ var import_types51 = require("@neat.is/types");
12077
12405
 
12078
12406
  // src/connectors/cloudflare/client.ts
12079
12407
  init_cjs_shims();
@@ -12189,7 +12517,7 @@ function mapEventToSignal(event) {
12189
12517
  if (Number.isNaN(observedAt.getTime())) return null;
12190
12518
  const statusCode = metadata?.statusCode;
12191
12519
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
12192
- const path58 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
12520
+ const path59 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
12193
12521
  return {
12194
12522
  targetKind: CLOUDFLARE_TARGET_KIND,
12195
12523
  targetName: scriptName,
@@ -12197,7 +12525,7 @@ function mapEventToSignal(event) {
12197
12525
  errorCount: isError ? 1 : 0,
12198
12526
  lastObservedIso: observedAt.toISOString(),
12199
12527
  method,
12200
- ...path58 ? { path: path58 } : {},
12528
+ ...path59 ? { path: path59 } : {},
12201
12529
  ...typeof statusCode === "number" ? { statusCode } : {},
12202
12530
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
12203
12531
  };
@@ -12237,19 +12565,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
12237
12565
  graph.forEachNode((id, attrs) => {
12238
12566
  if (found) return;
12239
12567
  const a = attrs;
12240
- if (a.type === import_types49.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
12568
+ if (a.type === import_types51.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
12241
12569
  found = id;
12242
12570
  }
12243
12571
  });
12244
12572
  return found;
12245
12573
  }
12246
- function findMatchingRouteNode(graph, serviceName, method, path58) {
12247
- const normalizedPath = normalizePathTemplate(path58);
12574
+ function findMatchingRouteNode(graph, serviceName, method, path59) {
12575
+ const normalizedPath = normalizePathTemplate(path59);
12248
12576
  let found = null;
12249
12577
  graph.forEachNode((id, attrs) => {
12250
12578
  if (found) return;
12251
12579
  const a = attrs;
12252
- if (a.type !== import_types49.NodeType.RouteNode || a.service !== serviceName) return;
12580
+ if (a.type !== import_types51.NodeType.RouteNode || a.service !== serviceName) return;
12253
12581
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
12254
12582
  const routeMethod = (a.method ?? "").toUpperCase();
12255
12583
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -12261,18 +12589,18 @@ function createCloudflareResolveTarget(config, graph) {
12261
12589
  return (signal) => {
12262
12590
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
12263
12591
  const scriptName = signal.targetName;
12264
- const { method, path: path58 } = signal;
12592
+ const { method, path: path59 } = signal;
12265
12593
  const resolveRouteGrain = (serviceName, wholeFileId) => {
12266
- if (!method || !path58) return wholeFileId;
12267
- return findMatchingRouteNode(graph, serviceName, method, path58) ?? wholeFileId;
12594
+ if (!method || !path59) return wholeFileId;
12595
+ return findMatchingRouteNode(graph, serviceName, method, path59) ?? wholeFileId;
12268
12596
  };
12269
12597
  const mapping = config.workers?.[scriptName];
12270
12598
  if (mapping) {
12271
- const wholeFileId = (0, import_types49.fileId)(mapping.service, mapping.entryFile);
12599
+ const wholeFileId = (0, import_types51.fileId)(mapping.service, mapping.entryFile);
12272
12600
  return {
12273
12601
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
12274
12602
  serviceName: mapping.service,
12275
- edgeType: import_types49.EdgeType.CALLS
12603
+ edgeType: import_types51.EdgeType.CALLS
12276
12604
  };
12277
12605
  }
12278
12606
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -12281,13 +12609,13 @@ function createCloudflareResolveTarget(config, graph) {
12281
12609
  return {
12282
12610
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
12283
12611
  serviceName: fileNode.service,
12284
- edgeType: import_types49.EdgeType.CALLS
12612
+ edgeType: import_types51.EdgeType.CALLS
12285
12613
  };
12286
12614
  }
12287
12615
  return {
12288
- targetNodeId: (0, import_types49.infraId)("cloudflare-worker", scriptName),
12616
+ targetNodeId: (0, import_types51.infraId)("cloudflare-worker", scriptName),
12289
12617
  serviceName: scriptName,
12290
- edgeType: import_types49.EdgeType.CALLS,
12618
+ edgeType: import_types51.EdgeType.CALLS,
12291
12619
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
12292
12620
  };
12293
12621
  };
@@ -12465,12 +12793,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
12465
12793
  if (!prior || calls <= prior.calls) continue;
12466
12794
  const table = tableFromSqlStatement(row.query);
12467
12795
  if (!table) continue;
12796
+ const columns = columnsFromSqlStatement(row.query);
12468
12797
  signals.push({
12469
12798
  targetKind: NEON_SQL_TABLE_TARGET_KIND,
12470
12799
  targetName: table,
12471
12800
  callCount: calls - prior.calls,
12472
12801
  errorCount: 0,
12473
- lastObservedIso: observedAtIso
12802
+ lastObservedIso: observedAtIso,
12803
+ ...columns.length > 0 ? { columns } : {}
12474
12804
  });
12475
12805
  }
12476
12806
  for (const queryid of previous.keys()) {
@@ -12481,14 +12811,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
12481
12811
 
12482
12812
  // src/connectors/neon/resolve.ts
12483
12813
  init_cjs_shims();
12484
- var import_types53 = require("@neat.is/types");
12814
+ var import_types55 = require("@neat.is/types");
12485
12815
  function createNeonResolveTarget(config) {
12486
12816
  return (signal) => {
12487
12817
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
12488
12818
  return {
12489
- targetNodeId: (0, import_types53.infraId)("sql-table", signal.targetName),
12819
+ targetNodeId: (0, import_types55.infraId)("sql-table", signal.targetName),
12490
12820
  serviceName: config.serviceName,
12491
- edgeType: import_types53.EdgeType.CALLS,
12821
+ edgeType: import_types55.EdgeType.CALLS,
12492
12822
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
12493
12823
  };
12494
12824
  };
@@ -13052,11 +13382,11 @@ function registerRoutes(scope, ctx) {
13052
13382
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
13053
13383
  const parsed = [];
13054
13384
  for (const c of candidates) {
13055
- const r = import_types56.DivergenceTypeSchema.safeParse(c);
13385
+ const r = import_types58.DivergenceTypeSchema.safeParse(c);
13056
13386
  if (!r.success) {
13057
13387
  return reply.code(400).send({
13058
13388
  error: `unknown divergence type "${c}"`,
13059
- allowed: import_types56.DivergenceTypeSchema.options
13389
+ allowed: import_types58.DivergenceTypeSchema.options
13060
13390
  });
13061
13391
  }
13062
13392
  parsed.push(r.data);
@@ -13365,7 +13695,7 @@ function registerRoutes(scope, ctx) {
13365
13695
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
13366
13696
  let violations = await log.readAll();
13367
13697
  if (req.query.severity) {
13368
- const sev = import_types56.PolicySeveritySchema.safeParse(req.query.severity);
13698
+ const sev = import_types58.PolicySeveritySchema.safeParse(req.query.severity);
13369
13699
  if (!sev.success) {
13370
13700
  return reply.code(400).send({
13371
13701
  error: "invalid severity",
@@ -13404,7 +13734,7 @@ function registerRoutes(scope, ctx) {
13404
13734
  scope.post("/policies/check", async (req, reply) => {
13405
13735
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
13406
13736
  if (!proj) return;
13407
- const parsed = import_types56.PoliciesCheckBodySchema.safeParse(req.body ?? {});
13737
+ const parsed = import_types58.PoliciesCheckBodySchema.safeParse(req.body ?? {});
13408
13738
  if (!parsed.success) {
13409
13739
  return reply.code(400).send({
13410
13740
  error: "invalid /policies/check body",
@@ -13726,7 +14056,7 @@ init_otel_grpc();
13726
14056
  // src/daemon.ts
13727
14057
  init_cjs_shims();
13728
14058
  var import_node_fs32 = require("fs");
13729
- var import_node_path57 = __toESM(require("path"), 1);
14059
+ var import_node_path58 = __toESM(require("path"), 1);
13730
14060
  var import_node_module = require("module");
13731
14061
  init_otel();
13732
14062
  init_auth();
@@ -13734,7 +14064,7 @@ init_auth();
13734
14064
  // src/unrouted.ts
13735
14065
  init_cjs_shims();
13736
14066
  var import_node_fs31 = require("fs");
13737
- var import_node_path56 = __toESM(require("path"), 1);
14067
+ var import_node_path57 = __toESM(require("path"), 1);
13738
14068
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
13739
14069
  return {
13740
14070
  timestamp: now.toISOString(),
@@ -13744,34 +14074,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
13744
14074
  };
13745
14075
  }
13746
14076
  async function appendUnroutedSpan(neatHome3, record) {
13747
- const target = import_node_path56.default.join(neatHome3, "errors.ndjson");
14077
+ const target = import_node_path57.default.join(neatHome3, "errors.ndjson");
13748
14078
  await import_node_fs31.promises.mkdir(neatHome3, { recursive: true });
13749
14079
  await import_node_fs31.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
13750
14080
  }
13751
14081
  function unroutedErrorsPath(neatHome3) {
13752
- return import_node_path56.default.join(neatHome3, "errors.ndjson");
14082
+ return import_node_path57.default.join(neatHome3, "errors.ndjson");
13753
14083
  }
13754
14084
 
13755
14085
  // src/daemon.ts
13756
- var import_types57 = require("@neat.is/types");
14086
+ var import_types59 = require("@neat.is/types");
13757
14087
  function daemonJsonPath(scanPath) {
13758
- return import_node_path57.default.join(scanPath, "neat-out", "daemon.json");
14088
+ return import_node_path58.default.join(scanPath, "neat-out", "daemon.json");
13759
14089
  }
13760
14090
  function daemonsDiscoveryDir(home) {
13761
14091
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
13762
- return import_node_path57.default.join(base, "daemons");
14092
+ return import_node_path58.default.join(base, "daemons");
13763
14093
  }
13764
14094
  function daemonDiscoveryPath(project, home) {
13765
- return import_node_path57.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
14095
+ return import_node_path58.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
13766
14096
  }
13767
14097
  function sanitizeDiscoveryName(project) {
13768
14098
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
13769
14099
  }
13770
14100
  function neatHomeFromEnv() {
13771
14101
  const env = process.env.NEAT_HOME;
13772
- if (env && env.length > 0) return import_node_path57.default.resolve(env);
14102
+ if (env && env.length > 0) return import_node_path58.default.resolve(env);
13773
14103
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
13774
- return import_node_path57.default.join(home, ".neat");
14104
+ return import_node_path58.default.join(home, ".neat");
13775
14105
  }
13776
14106
  function resolveNeatVersion() {
13777
14107
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -13826,11 +14156,11 @@ function teardownSlot(slot) {
13826
14156
  }
13827
14157
  }
13828
14158
  function neatHomeFor(opts) {
13829
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path57.default.resolve(opts.neatHome);
14159
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path58.default.resolve(opts.neatHome);
13830
14160
  const env = process.env.NEAT_HOME;
13831
- if (env && env.length > 0) return import_node_path57.default.resolve(env);
14161
+ if (env && env.length > 0) return import_node_path58.default.resolve(env);
13832
14162
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
13833
- return import_node_path57.default.join(home, ".neat");
14163
+ return import_node_path58.default.join(home, ".neat");
13834
14164
  }
13835
14165
  function routeSpanToProject(serviceName, projects) {
13836
14166
  if (!serviceName) return DEFAULT_PROJECT;
@@ -13878,11 +14208,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
13878
14208
  if (!serviceName) return true;
13879
14209
  if (serviceNameMatchesProject(serviceName, project)) return true;
13880
14210
  return graph.someNode(
13881
- (_id, attrs) => attrs.type === import_types57.NodeType.ServiceNode && attrs.name === serviceName
14211
+ (_id, attrs) => attrs.type === import_types59.NodeType.ServiceNode && attrs.name === serviceName
13882
14212
  );
13883
14213
  }
13884
14214
  async function bootstrapProject(entry, connectors = [], neatHome3) {
13885
- const paths = pathsForProject(entry.name, import_node_path57.default.join(entry.path, "neat-out"));
14215
+ const paths = pathsForProject(entry.name, import_node_path58.default.join(entry.path, "neat-out"));
13886
14216
  try {
13887
14217
  const stat = await import_node_fs32.promises.stat(entry.path);
13888
14218
  if (!stat.isDirectory()) {
@@ -13998,7 +14328,7 @@ async function startDaemon(opts = {}) {
13998
14328
  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;
13999
14329
  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;
14000
14330
  const singleProject = projectArg;
14001
- const singleProjectPath = singleProject && projectPathArg ? import_node_path57.default.resolve(projectPathArg) : null;
14331
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path58.default.resolve(projectPathArg) : null;
14002
14332
  if (singleProject && !singleProjectPath) {
14003
14333
  throw new Error(
14004
14334
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -14013,7 +14343,7 @@ async function startDaemon(opts = {}) {
14013
14343
  );
14014
14344
  }
14015
14345
  }
14016
- const pidPath = import_node_path57.default.join(home, "neatd.pid");
14346
+ const pidPath = import_node_path58.default.join(home, "neatd.pid");
14017
14347
  await writeAtomically(pidPath, `${process.pid}
14018
14348
  `);
14019
14349
  const slots = /* @__PURE__ */ new Map();
@@ -14425,8 +14755,8 @@ async function startDaemon(opts = {}) {
14425
14755
  let registryWatcher = null;
14426
14756
  let reloadTimer = null;
14427
14757
  if (!singleProject) try {
14428
- const regDir = import_node_path57.default.dirname(regPath);
14429
- const regBase = import_node_path57.default.basename(regPath);
14758
+ const regDir = import_node_path58.default.dirname(regPath);
14759
+ const regBase = import_node_path58.default.basename(regPath);
14430
14760
  registryWatcher = (0, import_node_fs32.watch)(regDir, (_eventType, filename) => {
14431
14761
  if (filename !== null && filename !== regBase) return;
14432
14762
  if (reloadTimer) clearTimeout(reloadTimer);