@neat.is/core 0.7.4 → 0.7.5

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/server.cjs CHANGED
@@ -736,7 +736,7 @@ function getGraph(project = DEFAULT_PROJECT) {
736
736
  init_cjs_shims();
737
737
  var import_fastify2 = __toESM(require("fastify"), 1);
738
738
  var import_cors = __toESM(require("@fastify/cors"), 1);
739
- var import_types59 = require("@neat.is/types");
739
+ var import_types66 = require("@neat.is/types");
740
740
 
741
741
  // src/extend/index.ts
742
742
  init_cjs_shims();
@@ -8947,6 +8947,55 @@ function columnsFromClassBody(body) {
8947
8947
  }
8948
8948
  return out;
8949
8949
  }
8950
+ function foreignKeyParentTable(call) {
8951
+ const fn = call.childForFieldName("function");
8952
+ const t = fn?.text;
8953
+ if (!t) return null;
8954
+ const base = t.includes(".") ? t.slice(t.lastIndexOf(".") + 1) : t;
8955
+ if (base !== "ForeignKey") return null;
8956
+ const target = firstPositionalString(call);
8957
+ if (!target) return null;
8958
+ const parts = target.split(".");
8959
+ if (parts.length < 2) return null;
8960
+ return parts[parts.length - 2];
8961
+ }
8962
+ function sqlalchemyForeignKeys(file, serviceDir) {
8963
+ if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
8964
+ const tree = parseSource6(makePyParser4(), file.content);
8965
+ const out = [];
8966
+ const seen = /* @__PURE__ */ new Set();
8967
+ walk3(tree.rootNode, (node) => {
8968
+ if (node.type !== "class_definition") return;
8969
+ const body = node.childForFieldName("body");
8970
+ const nameNode = node.childForFieldName("name");
8971
+ if (!body || !nameNode) return;
8972
+ const explicit = explicitTablename(body);
8973
+ if (explicit === "computed") return;
8974
+ let childTable = null;
8975
+ if (explicit) childTable = explicit.name;
8976
+ else if (extendsFlaskModel(node)) childTable = flaskSqlalchemyTableName(nameNode.text);
8977
+ if (!childTable) return;
8978
+ walk3(body, (n) => {
8979
+ if (n.type !== "call") return;
8980
+ const parentTable = foreignKeyParentTable(n);
8981
+ if (!parentTable) return;
8982
+ const key = `${childTable}->${parentTable}`;
8983
+ if (seen.has(key)) return;
8984
+ seen.add(key);
8985
+ const line = n.startPosition.row + 1;
8986
+ out.push({
8987
+ childTable,
8988
+ parentTable,
8989
+ evidence: {
8990
+ file: import_node_path37.default.relative(serviceDir, file.path),
8991
+ line,
8992
+ snippet: snippet(file.content, line)
8993
+ }
8994
+ });
8995
+ });
8996
+ });
8997
+ return out;
8998
+ }
8950
8999
  function sqlalchemyEndpointsFromFile(file, serviceDir) {
8951
9000
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
8952
9001
  const tree = parseSource6(makePyParser4(), file.content);
@@ -9292,6 +9341,92 @@ function drizzleEndpointsFromFile(file, serviceDir) {
9292
9341
  walk6(tree.rootNode);
9293
9342
  return out;
9294
9343
  }
9344
+ function enclosingVarName(call) {
9345
+ let node = call;
9346
+ while (node?.parent) {
9347
+ const parent = node.parent;
9348
+ if (parent.type === "variable_declarator") {
9349
+ const name = parent.childForFieldName("name");
9350
+ return name?.type === "identifier" ? name.text : null;
9351
+ }
9352
+ if (parent.type === "call_expression" || parent.type === "member_expression") {
9353
+ node = parent;
9354
+ continue;
9355
+ }
9356
+ return null;
9357
+ }
9358
+ return null;
9359
+ }
9360
+ function collectDrizzleTables(root) {
9361
+ const tables = [];
9362
+ const varToTable = /* @__PURE__ */ new Map();
9363
+ const walk6 = (node) => {
9364
+ if (node.type === "call_expression") {
9365
+ const fn = node.childForFieldName("function");
9366
+ if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
9367
+ const args = node.childForFieldName("arguments");
9368
+ const argNodes = args ? namedChildren3(args) : [];
9369
+ const tableName = stringLiteralText2(argNodes[0] ?? null);
9370
+ const obj = argNodes[1]?.type === "object" ? argNodes[1] : null;
9371
+ if (tableName) {
9372
+ tables.push({ tableName, object: obj });
9373
+ const varName = enclosingVarName(node);
9374
+ if (varName) varToTable.set(varName, tableName);
9375
+ }
9376
+ }
9377
+ }
9378
+ for (const c of namedChildren3(node)) walk6(c);
9379
+ };
9380
+ walk6(root);
9381
+ return { tables, varToTable };
9382
+ }
9383
+ function referencesTargetVar(call) {
9384
+ const fn = call.childForFieldName("function");
9385
+ if (fn?.type !== "member_expression") return null;
9386
+ if (fn.childForFieldName("property")?.text !== "references") return null;
9387
+ const args = call.childForFieldName("arguments");
9388
+ const first = args ? namedChildren3(args)[0] ?? null : null;
9389
+ if (first?.type !== "arrow_function") return null;
9390
+ const body = first.childForFieldName("body");
9391
+ if (body?.type !== "member_expression") return null;
9392
+ const obj = body.childForFieldName("object");
9393
+ return obj?.type === "identifier" ? obj.text : null;
9394
+ }
9395
+ function drizzleForeignKeys(file, serviceDir) {
9396
+ if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
9397
+ const tree = parseSource3(parserForExt(import_node_path39.default.extname(file.path)), file.content);
9398
+ const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
9399
+ const out = [];
9400
+ const seen = /* @__PURE__ */ new Set();
9401
+ for (const table of tables) {
9402
+ if (!table.object) continue;
9403
+ const walk6 = (node) => {
9404
+ if (node.type === "call_expression") {
9405
+ const targetVar = referencesTargetVar(node);
9406
+ const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
9407
+ if (parentTable) {
9408
+ const key = `${table.tableName}->${parentTable}`;
9409
+ if (!seen.has(key)) {
9410
+ seen.add(key);
9411
+ const line = node.startPosition.row + 1;
9412
+ out.push({
9413
+ childTable: table.tableName,
9414
+ parentTable,
9415
+ evidence: {
9416
+ file: import_node_path39.default.relative(serviceDir, file.path),
9417
+ line,
9418
+ snippet: snippet(file.content, line)
9419
+ }
9420
+ });
9421
+ }
9422
+ }
9423
+ }
9424
+ for (const c of namedChildren3(node)) walk6(c);
9425
+ };
9426
+ walk6(table.object);
9427
+ }
9428
+ return out;
9429
+ }
9295
9430
 
9296
9431
  // src/extract/calls/prisma.ts
9297
9432
  init_cjs_shims();
@@ -9420,6 +9555,96 @@ async function prismaColumnEndpoints(serviceDir) {
9420
9555
  if (!content) return [];
9421
9556
  return prismaColumnsFromSchema({ path: schemaPath, content }, serviceDir);
9422
9557
  }
9558
+ function buildModelTableMap(lines) {
9559
+ const map = /* @__PURE__ */ new Map();
9560
+ let current = null;
9561
+ let depth = 0;
9562
+ for (const raw of lines) {
9563
+ if (current === null) {
9564
+ const header = raw.match(/^\s*model\s+([A-Za-z_]\w*)\b/);
9565
+ if (header && raw.includes("{")) {
9566
+ current = { model: header[1], table: header[1] };
9567
+ depth = netBraces(raw);
9568
+ if (depth <= 0) {
9569
+ map.set(current.model, current.table);
9570
+ current = null;
9571
+ }
9572
+ }
9573
+ continue;
9574
+ }
9575
+ depth += netBraces(raw);
9576
+ const trimmed = stripLineComment(raw).trim();
9577
+ if (trimmed.startsWith("@@")) {
9578
+ const m = trimmed.match(/@@map\(\s*"([^"]+)"\s*\)/);
9579
+ if (m) current.table = m[1];
9580
+ }
9581
+ if (depth <= 0) {
9582
+ map.set(current.model, current.table);
9583
+ current = null;
9584
+ }
9585
+ }
9586
+ if (current) map.set(current.model, current.table);
9587
+ return map;
9588
+ }
9589
+ function prismaForeignKeysFromSchema(file, serviceDir) {
9590
+ const content = file.content;
9591
+ if (!/\bmodel\s+[A-Za-z_]\w*\s*\{/.test(content)) return [];
9592
+ const lines = content.split("\n");
9593
+ const modelToTable = buildModelTableMap(lines);
9594
+ const out = [];
9595
+ const seen = /* @__PURE__ */ new Set();
9596
+ let current = null;
9597
+ let depth = 0;
9598
+ for (let i = 0; i < lines.length; i++) {
9599
+ const raw = lines[i];
9600
+ const lineNo = i + 1;
9601
+ if (current === null) {
9602
+ const header = raw.match(/^\s*model\s+([A-Za-z_]\w*)\b/);
9603
+ if (header && raw.includes("{")) {
9604
+ current = { table: modelToTable.get(header[1]) ?? header[1] };
9605
+ depth = netBraces(raw);
9606
+ if (depth <= 0) current = null;
9607
+ }
9608
+ continue;
9609
+ }
9610
+ depth += netBraces(raw);
9611
+ const closing = depth <= 0;
9612
+ const trimmed = stripLineComment(raw).trim();
9613
+ if (trimmed && !trimmed.startsWith("@@") && !trimmed.startsWith("}")) {
9614
+ const fm = trimmed.match(/^([A-Za-z_]\w*)\s+([A-Za-z_]\w*)/);
9615
+ if (fm && /@relation\b[^)]*\bfields\s*:/.test(trimmed)) {
9616
+ const parentTable = modelToTable.get(fm[2]);
9617
+ if (parentTable) {
9618
+ const key = `${current.table}->${parentTable}`;
9619
+ if (!seen.has(key)) {
9620
+ seen.add(key);
9621
+ out.push({
9622
+ childTable: current.table,
9623
+ parentTable,
9624
+ evidence: {
9625
+ file: import_node_path40.default.relative(serviceDir, file.path),
9626
+ line: lineNo,
9627
+ snippet: snippet(content, lineNo)
9628
+ }
9629
+ });
9630
+ }
9631
+ }
9632
+ }
9633
+ }
9634
+ if (closing) current = null;
9635
+ }
9636
+ return out;
9637
+ }
9638
+ async function prismaForeignKeys(serviceDir) {
9639
+ const schemaPath = await findFirst(serviceDir, [
9640
+ import_node_path40.default.join("prisma", "schema.prisma"),
9641
+ "schema.prisma"
9642
+ ]);
9643
+ if (!schemaPath) return [];
9644
+ const content = await readIfExists(schemaPath);
9645
+ if (!content) return [];
9646
+ return prismaForeignKeysFromSchema({ path: schemaPath, content }, serviceDir);
9647
+ }
9423
9648
 
9424
9649
  // src/extract/calls/go.ts
9425
9650
  init_cjs_shims();
@@ -9596,21 +9821,79 @@ async function addCallEdges(graph, services) {
9596
9821
  };
9597
9822
  }
9598
9823
 
9824
+ // src/extract/table-edges.ts
9825
+ init_cjs_shims();
9826
+ var import_types32 = require("@neat.is/types");
9827
+ async function addTableEdges(graph, services) {
9828
+ let nodesAdded = 0;
9829
+ let edgesAdded = 0;
9830
+ for (const service of services) {
9831
+ const files = await loadSourceFiles(service.dir);
9832
+ const refs = [];
9833
+ for (const file of files) {
9834
+ try {
9835
+ refs.push(...drizzleForeignKeys(file, service.dir));
9836
+ refs.push(...sqlalchemyForeignKeys(file, service.dir));
9837
+ } catch (err) {
9838
+ recordExtractionError("foreign-key extraction", file.path, err);
9839
+ }
9840
+ }
9841
+ try {
9842
+ refs.push(...await prismaForeignKeys(service.dir));
9843
+ } catch (err) {
9844
+ recordExtractionError("prisma foreign-key extraction", service.dir, err);
9845
+ }
9846
+ for (const ref of refs) {
9847
+ const childId = (0, import_types32.infraId)("sql-table", ref.childTable);
9848
+ const parentId = (0, import_types32.infraId)("sql-table", ref.parentTable);
9849
+ if (childId === parentId) continue;
9850
+ nodesAdded += ensureTableNode(graph, childId, ref.childTable);
9851
+ nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
9852
+ const edgeId = (0, import_types32.extractedEdgeId)(childId, parentId, import_types32.EdgeType.REFERENCES);
9853
+ if (graph.hasEdge(edgeId)) continue;
9854
+ const edge = {
9855
+ id: edgeId,
9856
+ source: childId,
9857
+ target: parentId,
9858
+ type: import_types32.EdgeType.REFERENCES,
9859
+ provenance: import_types32.Provenance.EXTRACTED,
9860
+ confidence: (0, import_types32.confidenceForExtracted)("structural"),
9861
+ evidence: ref.evidence
9862
+ };
9863
+ graph.addEdgeWithKey(edgeId, childId, parentId, edge);
9864
+ edgesAdded++;
9865
+ }
9866
+ }
9867
+ return { nodesAdded, edgesAdded };
9868
+ }
9869
+ function ensureTableNode(graph, id, name) {
9870
+ if (graph.hasNode(id)) return 0;
9871
+ const node = {
9872
+ id,
9873
+ type: import_types32.NodeType.InfraNode,
9874
+ name,
9875
+ provider: "self",
9876
+ kind: "sql-table"
9877
+ };
9878
+ graph.addNode(id, node);
9879
+ return 1;
9880
+ }
9881
+
9599
9882
  // src/extract/infra/index.ts
9600
9883
  init_cjs_shims();
9601
9884
 
9602
9885
  // src/extract/infra/docker-compose.ts
9603
9886
  init_cjs_shims();
9604
9887
  var import_node_path44 = __toESM(require("path"), 1);
9605
- var import_types33 = require("@neat.is/types");
9888
+ var import_types34 = require("@neat.is/types");
9606
9889
 
9607
9890
  // src/extract/infra/shared.ts
9608
9891
  init_cjs_shims();
9609
- var import_types32 = require("@neat.is/types");
9892
+ var import_types33 = require("@neat.is/types");
9610
9893
  function makeInfraNode(kind, name, provider = "self", extras) {
9611
9894
  return {
9612
- id: (0, import_types32.infraId)(kind, name),
9613
- type: import_types32.NodeType.InfraNode,
9895
+ id: (0, import_types33.infraId)(kind, name),
9896
+ type: import_types33.NodeType.InfraNode,
9614
9897
  name,
9615
9898
  provider,
9616
9899
  kind,
@@ -9654,8 +9937,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
9654
9937
  source: anchorId,
9655
9938
  target: node.id,
9656
9939
  type: edgeType,
9657
- provenance: import_types32.Provenance.EXTRACTED,
9658
- confidence: (0, import_types32.confidenceForExtracted)("structural"),
9940
+ provenance: import_types33.Provenance.EXTRACTED,
9941
+ confidence: (0, import_types33.confidenceForExtracted)("structural"),
9659
9942
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
9660
9943
  };
9661
9944
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -9722,15 +10005,15 @@ async function addComposeInfra(graph, scanPath, services) {
9722
10005
  for (const dep of dependsOnList(svc.depends_on)) {
9723
10006
  const targetId = composeNameToNodeId.get(dep);
9724
10007
  if (!targetId) continue;
9725
- const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types33.EdgeType.DEPENDS_ON);
10008
+ const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types34.EdgeType.DEPENDS_ON);
9726
10009
  if (graph.hasEdge(edgeId)) continue;
9727
10010
  const edge = {
9728
10011
  id: edgeId,
9729
10012
  source: sourceId,
9730
10013
  target: targetId,
9731
- type: import_types33.EdgeType.DEPENDS_ON,
9732
- provenance: import_types33.Provenance.EXTRACTED,
9733
- confidence: (0, import_types33.confidenceForExtracted)("structural"),
10014
+ type: import_types34.EdgeType.DEPENDS_ON,
10015
+ provenance: import_types34.Provenance.EXTRACTED,
10016
+ confidence: (0, import_types34.confidenceForExtracted)("structural"),
9734
10017
  evidence: { file: evidenceFile }
9735
10018
  };
9736
10019
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -9744,7 +10027,7 @@ async function addComposeInfra(graph, scanPath, services) {
9744
10027
  init_cjs_shims();
9745
10028
  var import_node_path45 = __toESM(require("path"), 1);
9746
10029
  var import_node_fs19 = require("fs");
9747
- var import_types34 = require("@neat.is/types");
10030
+ var import_types35 = require("@neat.is/types");
9748
10031
  function readDockerfile(content) {
9749
10032
  let image = null;
9750
10033
  const ports = [];
@@ -9803,15 +10086,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
9803
10086
  );
9804
10087
  nodesAdded += fn;
9805
10088
  edgesAdded += fe;
9806
- const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types34.EdgeType.RUNS_ON);
10089
+ const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types35.EdgeType.RUNS_ON);
9807
10090
  if (!graph.hasEdge(edgeId)) {
9808
10091
  const edge = {
9809
10092
  id: edgeId,
9810
10093
  source: fileNodeId,
9811
10094
  target: node.id,
9812
- type: import_types34.EdgeType.RUNS_ON,
9813
- provenance: import_types34.Provenance.EXTRACTED,
9814
- confidence: (0, import_types34.confidenceForExtracted)("structural"),
10095
+ type: import_types35.EdgeType.RUNS_ON,
10096
+ provenance: import_types35.Provenance.EXTRACTED,
10097
+ confidence: (0, import_types35.confidenceForExtracted)("structural"),
9815
10098
  evidence: {
9816
10099
  file: evidenceFile,
9817
10100
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -9826,15 +10109,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
9826
10109
  graph.addNode(portNode.id, portNode);
9827
10110
  nodesAdded++;
9828
10111
  }
9829
- const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types34.EdgeType.CONNECTS_TO);
10112
+ const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types35.EdgeType.CONNECTS_TO);
9830
10113
  if (graph.hasEdge(portEdgeId)) continue;
9831
10114
  const portEdge = {
9832
10115
  id: portEdgeId,
9833
10116
  source: fileNodeId,
9834
10117
  target: portNode.id,
9835
- type: import_types34.EdgeType.CONNECTS_TO,
9836
- provenance: import_types34.Provenance.EXTRACTED,
9837
- confidence: (0, import_types34.confidenceForExtracted)("structural"),
10118
+ type: import_types35.EdgeType.CONNECTS_TO,
10119
+ provenance: import_types35.Provenance.EXTRACTED,
10120
+ confidence: (0, import_types35.confidenceForExtracted)("structural"),
9838
10121
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
9839
10122
  };
9840
10123
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -9848,7 +10131,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
9848
10131
  init_cjs_shims();
9849
10132
  var import_node_fs20 = require("fs");
9850
10133
  var import_node_path46 = __toESM(require("path"), 1);
9851
- var import_types35 = require("@neat.is/types");
10134
+ var import_types36 = require("@neat.is/types");
9852
10135
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
9853
10136
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
9854
10137
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -9929,16 +10212,16 @@ async function addTerraformResources(graph, scanPath) {
9929
10212
  if (!target) continue;
9930
10213
  if (seen.has(target.nodeId)) continue;
9931
10214
  seen.add(target.nodeId);
9932
- const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types35.EdgeType.DEPENDS_ON);
10215
+ const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types36.EdgeType.DEPENDS_ON);
9933
10216
  if (graph.hasEdge(edgeId)) continue;
9934
10217
  const line = lineAt2(content, resource.bodyOffset + ref.index);
9935
10218
  const edge = {
9936
10219
  id: edgeId,
9937
10220
  source: resource.nodeId,
9938
10221
  target: target.nodeId,
9939
- type: import_types35.EdgeType.DEPENDS_ON,
9940
- provenance: import_types35.Provenance.EXTRACTED,
9941
- confidence: (0, import_types35.confidenceForExtracted)("structural"),
10222
+ type: import_types36.EdgeType.DEPENDS_ON,
10223
+ provenance: import_types36.Provenance.EXTRACTED,
10224
+ confidence: (0, import_types36.confidenceForExtracted)("structural"),
9942
10225
  evidence: { file: evidenceFile, line, snippet: key }
9943
10226
  };
9944
10227
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -10010,7 +10293,7 @@ init_cjs_shims();
10010
10293
  var import_node_fs22 = require("fs");
10011
10294
  var import_node_path48 = __toESM(require("path"), 1);
10012
10295
  var import_smol_toml2 = require("smol-toml");
10013
- var import_types36 = require("@neat.is/types");
10296
+ var import_types37 = require("@neat.is/types");
10014
10297
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
10015
10298
  async function readWranglerConfig(dir) {
10016
10299
  for (const filename of WRANGLER_FILENAMES) {
@@ -10058,8 +10341,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
10058
10341
  source: anchorId,
10059
10342
  target: node.id,
10060
10343
  type: edgeType,
10061
- provenance: import_types36.Provenance.EXTRACTED,
10062
- confidence: (0, import_types36.confidenceForExtracted)("structural"),
10344
+ provenance: import_types37.Provenance.EXTRACTED,
10345
+ confidence: (0, import_types37.confidenceForExtracted)("structural"),
10063
10346
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
10064
10347
  };
10065
10348
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -10120,15 +10403,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
10120
10403
  nodesAdded++;
10121
10404
  }
10122
10405
  if (runtimeNode.id !== anchorId) {
10123
- const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types36.EdgeType.RUNS_ON);
10406
+ const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types37.EdgeType.RUNS_ON);
10124
10407
  if (!graph.hasEdge(runsOnId)) {
10125
10408
  const edge = {
10126
10409
  id: runsOnId,
10127
10410
  source: anchorId,
10128
10411
  target: runtimeNode.id,
10129
- type: import_types36.EdgeType.RUNS_ON,
10130
- provenance: import_types36.Provenance.EXTRACTED,
10131
- confidence: (0, import_types36.confidenceForExtracted)("structural"),
10412
+ type: import_types37.EdgeType.RUNS_ON,
10413
+ provenance: import_types37.Provenance.EXTRACTED,
10414
+ confidence: (0, import_types37.confidenceForExtracted)("structural"),
10132
10415
  evidence: {
10133
10416
  file: evidenceFile,
10134
10417
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -10142,7 +10425,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
10142
10425
  const result = addResourceEdge(
10143
10426
  graph,
10144
10427
  anchorId,
10145
- import_types36.EdgeType.CONNECTS_TO,
10428
+ import_types37.EdgeType.CONNECTS_TO,
10146
10429
  "cloudflare-route",
10147
10430
  route,
10148
10431
  evidenceFile,
@@ -10166,7 +10449,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
10166
10449
  const result = addResourceEdge(
10167
10450
  graph,
10168
10451
  anchorId,
10169
- import_types36.EdgeType.DEPENDS_ON,
10452
+ import_types37.EdgeType.DEPENDS_ON,
10170
10453
  group.kind,
10171
10454
  name,
10172
10455
  evidenceFile,
@@ -10180,7 +10463,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
10180
10463
  const result = addResourceEdge(
10181
10464
  graph,
10182
10465
  anchorId,
10183
- import_types36.EdgeType.DEPENDS_ON,
10466
+ import_types37.EdgeType.DEPENDS_ON,
10184
10467
  "cloudflare-cron",
10185
10468
  cron,
10186
10469
  evidenceFile,
@@ -10193,7 +10476,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
10193
10476
  const result = addResourceEdge(
10194
10477
  graph,
10195
10478
  anchorId,
10196
- import_types36.EdgeType.DEPENDS_ON,
10479
+ import_types37.EdgeType.DEPENDS_ON,
10197
10480
  "cloudflare-env-var",
10198
10481
  varName,
10199
10482
  evidenceFile,
@@ -10206,15 +10489,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
10206
10489
  if (!svc.service) continue;
10207
10490
  const target = workerIndex.get(svc.service);
10208
10491
  if (target && target.anchorId !== anchorId) {
10209
- const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types36.EdgeType.CALLS);
10492
+ const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types37.EdgeType.CALLS);
10210
10493
  if (!graph.hasEdge(edgeId)) {
10211
10494
  const edge = {
10212
10495
  id: edgeId,
10213
10496
  source: anchorId,
10214
10497
  target: target.anchorId,
10215
- type: import_types36.EdgeType.CALLS,
10216
- provenance: import_types36.Provenance.EXTRACTED,
10217
- confidence: (0, import_types36.confidenceForExtracted)("structural"),
10498
+ type: import_types37.EdgeType.CALLS,
10499
+ provenance: import_types37.Provenance.EXTRACTED,
10500
+ confidence: (0, import_types37.confidenceForExtracted)("structural"),
10218
10501
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
10219
10502
  };
10220
10503
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -10225,7 +10508,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
10225
10508
  const result = addResourceEdge(
10226
10509
  graph,
10227
10510
  anchorId,
10228
- import_types36.EdgeType.DEPENDS_ON,
10511
+ import_types37.EdgeType.DEPENDS_ON,
10229
10512
  "cloudflare-service-binding",
10230
10513
  svc.service,
10231
10514
  evidenceFile,
@@ -10242,7 +10525,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
10242
10525
  init_cjs_shims();
10243
10526
  var import_node_fs23 = require("fs");
10244
10527
  var import_node_path49 = __toESM(require("path"), 1);
10245
- var import_types37 = require("@neat.is/types");
10528
+ var import_types38 = require("@neat.is/types");
10246
10529
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
10247
10530
  async function readVercelConfig(dir) {
10248
10531
  for (const filename of VERCEL_CONFIG_FILENAMES) {
@@ -10305,12 +10588,12 @@ async function addVercelServices(graph, services, scanPath) {
10305
10588
  nodesAdded += result.nodesAdded;
10306
10589
  edgesAdded += result.edgesAdded;
10307
10590
  };
10308
- add(import_types37.EdgeType.RUNS_ON, "vercel", "vercel");
10309
- for (const cron of config.crons ?? []) add(import_types37.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
10310
- for (const varName of Object.keys(config.env ?? {})) add(import_types37.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
10311
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types37.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
10591
+ add(import_types38.EdgeType.RUNS_ON, "vercel", "vercel");
10592
+ for (const cron of config.crons ?? []) add(import_types38.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
10593
+ for (const varName of Object.keys(config.env ?? {})) add(import_types38.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
10594
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types38.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
10312
10595
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
10313
- add(import_types37.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
10596
+ add(import_types38.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
10314
10597
  }
10315
10598
  }
10316
10599
  return { nodesAdded, edgesAdded };
@@ -10321,7 +10604,7 @@ init_cjs_shims();
10321
10604
  var import_node_fs24 = require("fs");
10322
10605
  var import_node_path50 = __toESM(require("path"), 1);
10323
10606
  var import_smol_toml3 = require("smol-toml");
10324
- var import_types38 = require("@neat.is/types");
10607
+ var import_types39 = require("@neat.is/types");
10325
10608
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
10326
10609
  async function readRailwayConfig(dir) {
10327
10610
  for (const filename of RAILWAY_FILENAMES) {
@@ -10367,9 +10650,9 @@ async function addRailwayServices(graph, services, scanPath) {
10367
10650
  nodesAdded += result.nodesAdded;
10368
10651
  edgesAdded += result.edgesAdded;
10369
10652
  };
10370
- add(import_types38.EdgeType.RUNS_ON, "railway", "railway");
10371
- add(import_types38.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
10372
- add(import_types38.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
10653
+ add(import_types39.EdgeType.RUNS_ON, "railway", "railway");
10654
+ add(import_types39.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
10655
+ add(import_types39.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
10373
10656
  }
10374
10657
  return { nodesAdded, edgesAdded };
10375
10658
  }
@@ -10379,7 +10662,7 @@ init_cjs_shims();
10379
10662
  var import_node_fs25 = require("fs");
10380
10663
  var import_node_path51 = __toESM(require("path"), 1);
10381
10664
  var import_smol_toml4 = require("smol-toml");
10382
- var import_types39 = require("@neat.is/types");
10665
+ var import_types40 = require("@neat.is/types");
10383
10666
  async function readSupabaseConfig(dir) {
10384
10667
  const relFile = import_node_path51.default.join("supabase", "config.toml");
10385
10668
  const abs = import_node_path51.default.join(dir, relFile);
@@ -10427,10 +10710,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
10427
10710
  nodesAdded += result.nodesAdded;
10428
10711
  edgesAdded += result.edgesAdded;
10429
10712
  };
10430
- add(import_types39.EdgeType.RUNS_ON, "supabase", "supabase");
10431
- for (const fn of Object.keys(config.functions ?? {})) add(import_types39.EdgeType.DEPENDS_ON, "supabase-function", fn);
10432
- if (config.storage) add(import_types39.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
10433
- if (config.auth) add(import_types39.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
10713
+ add(import_types40.EdgeType.RUNS_ON, "supabase", "supabase");
10714
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types40.EdgeType.DEPENDS_ON, "supabase-function", fn);
10715
+ if (config.storage) add(import_types40.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
10716
+ if (config.auth) add(import_types40.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
10434
10717
  }
10435
10718
  return { nodesAdded, edgesAdded };
10436
10719
  }
@@ -10458,11 +10741,11 @@ var import_node_path53 = __toESM(require("path"), 1);
10458
10741
  init_cjs_shims();
10459
10742
  var import_node_fs26 = require("fs");
10460
10743
  var import_node_path52 = __toESM(require("path"), 1);
10461
- var import_types40 = require("@neat.is/types");
10744
+ var import_types41 = require("@neat.is/types");
10462
10745
  function dropOrphanedFileNodes(graph) {
10463
10746
  const orphans = [];
10464
10747
  graph.forEachNode((id, attrs) => {
10465
- if (attrs.type !== import_types40.NodeType.FileNode) return;
10748
+ if (attrs.type !== import_types41.NodeType.FileNode) return;
10466
10749
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
10467
10750
  orphans.push(id);
10468
10751
  }
@@ -10475,7 +10758,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
10475
10758
  const bases = [scanPath, ...serviceDirs];
10476
10759
  graph.forEachEdge((id, attrs) => {
10477
10760
  const edge = attrs;
10478
- if (edge.provenance !== import_types40.Provenance.EXTRACTED) return;
10761
+ if (edge.provenance !== import_types41.Provenance.EXTRACTED) return;
10479
10762
  const evidenceFile = edge.evidence?.file;
10480
10763
  if (!evidenceFile) return;
10481
10764
  if (import_node_path52.default.isAbsolute(evidenceFile)) {
@@ -10506,6 +10789,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
10506
10789
  const routePhase = await addRoutes(graph, services);
10507
10790
  const grpcPhase = await addGrpcMethods(graph, services);
10508
10791
  const phase4 = await addCallEdges(graph, services);
10792
+ const tableEdges = await addTableEdges(graph, services);
10509
10793
  const phase5 = await addInfra(graph, scanPath, services);
10510
10794
  const ghostsRetired = retireExtractedEdgesByMissingFile(
10511
10795
  graph,
@@ -10545,8 +10829,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
10545
10829
  }
10546
10830
  }
10547
10831
  const result = {
10548
- nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
10549
- edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
10832
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + tableEdges.nodesAdded + phase5.nodesAdded,
10833
+ edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + tableEdges.edgesAdded + phase5.edgesAdded,
10550
10834
  frontiersPromoted,
10551
10835
  extractionErrors: errorEntries.length,
10552
10836
  errorEntries,
@@ -10648,7 +10932,7 @@ function canonicalJson(value) {
10648
10932
  init_cjs_shims();
10649
10933
  var import_node_fs28 = require("fs");
10650
10934
  var import_node_path54 = __toESM(require("path"), 1);
10651
- var import_types41 = require("@neat.is/types");
10935
+ var import_types42 = require("@neat.is/types");
10652
10936
  var SCHEMA_VERSION = 6;
10653
10937
  function migrateV1ToV2(payload) {
10654
10938
  const nodes = payload.graph.nodes;
@@ -10672,7 +10956,7 @@ function migrateV5ToV6(payload) {
10672
10956
  if (Array.isArray(nodes)) {
10673
10957
  for (const node of nodes) {
10674
10958
  const attrs = node.attributes;
10675
- if (!attrs || attrs.type !== import_types41.NodeType.InfraNode) continue;
10959
+ if (!attrs || attrs.type !== import_types42.NodeType.InfraNode) continue;
10676
10960
  if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
10677
10961
  if (!Array.isArray(attrs.columns)) attrs.columns = [];
10678
10962
  }
@@ -10685,12 +10969,12 @@ function migrateV2ToV3(payload) {
10685
10969
  for (const edge of edges) {
10686
10970
  const attrs = edge.attributes;
10687
10971
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
10688
- attrs.provenance = import_types41.Provenance.OBSERVED;
10972
+ attrs.provenance = import_types42.Provenance.OBSERVED;
10689
10973
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
10690
10974
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
10691
10975
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
10692
10976
  if (type && source && target) {
10693
- const newId = (0, import_types41.observedEdgeId)(source, target, type);
10977
+ const newId = (0, import_types42.observedEdgeId)(source, target, type);
10694
10978
  attrs.id = newId;
10695
10979
  if (edge.key) edge.key = newId;
10696
10980
  }
@@ -10845,7 +11129,7 @@ init_cjs_shims();
10845
11129
  var import_node_fs29 = require("fs");
10846
11130
  var import_node_os3 = __toESM(require("os"), 1);
10847
11131
  var import_node_path56 = __toESM(require("path"), 1);
10848
- var import_types42 = require("@neat.is/types");
11132
+ var import_types43 = require("@neat.is/types");
10849
11133
  function neatHome() {
10850
11134
  const override = process.env.NEAT_HOME;
10851
11135
  if (override && override.length > 0) return import_node_path56.default.resolve(override);
@@ -10935,7 +11219,7 @@ async function readRegistry() {
10935
11219
  throw err;
10936
11220
  }
10937
11221
  const parsed = JSON.parse(raw);
10938
- return import_types42.RegistryFileSchema.parse(parsed);
11222
+ return import_types43.RegistryFileSchema.parse(parsed);
10939
11223
  }
10940
11224
  async function getProject(name) {
10941
11225
  const reg = await readRegistry();
@@ -11216,15 +11500,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
11216
11500
 
11217
11501
  // src/connectors/index.ts
11218
11502
  init_cjs_shims();
11219
- var import_types43 = require("@neat.is/types");
11503
+ var import_types44 = require("@neat.is/types");
11220
11504
  var NO_ENV = "unknown";
11221
11505
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
11222
11506
  if (!graph.hasNode(targetNodeId)) return void 0;
11223
11507
  const sites = [];
11224
11508
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
11225
11509
  const edge = graph.getEdgeAttributes(edgeId);
11226
- if (edge.provenance !== import_types43.Provenance.EXTRACTED) continue;
11227
- const parsed = (0, import_types43.parseFileId)(edge.source);
11510
+ if (edge.provenance !== import_types44.Provenance.EXTRACTED) continue;
11511
+ const parsed = (0, import_types44.parseFileId)(edge.source);
11228
11512
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
11229
11513
  const site = { relPath: edge.evidence.file };
11230
11514
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -11235,7 +11519,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
11235
11519
  function routeCallSiteFor(graph, targetNodeId) {
11236
11520
  if (!graph.hasNode(targetNodeId)) return void 0;
11237
11521
  const attrs = graph.getNodeAttributes(targetNodeId);
11238
- if (attrs.type !== import_types43.NodeType.RouteNode || !attrs.path) return void 0;
11522
+ if (attrs.type !== import_types44.NodeType.RouteNode || !attrs.path) return void 0;
11239
11523
  const site = { relPath: attrs.path };
11240
11524
  if (attrs.line !== void 0) site.line = attrs.line;
11241
11525
  return site;
@@ -11378,7 +11662,11 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
11378
11662
  // connector add/remove/test` (provision/deprovision/validate), never a poll
11379
11663
  // loop, so this bucket is exercised a handful of times per command. Kept
11380
11664
  // conservative pending a documented Drains-API rate limit.
11381
- vercel: { capacity: 20, refillMs: 5e3 }
11665
+ vercel: { capacity: 20, refillMs: 5e3 },
11666
+ // Render's REST API (api-docs.render.com/reference/rate-limiting) isn't
11667
+ // pinned here to a single confirmed number — this is a conservative
11668
+ // placeholder pending a live project, matching the other pull providers.
11669
+ render: { capacity: 30, refillMs: 1e4 }
11382
11670
  };
11383
11671
  var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
11384
11672
  function defaultRateLimitFor(provider) {
@@ -11785,23 +12073,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
11785
12073
 
11786
12074
  // src/connectors/supabase/resolve.ts
11787
12075
  init_cjs_shims();
11788
- var import_types45 = require("@neat.is/types");
12076
+ var import_types46 = require("@neat.is/types");
11789
12077
  function createSupabaseResolveTarget(graph, config) {
11790
12078
  return (signal, _ctx) => {
11791
12079
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
11792
12080
  return null;
11793
12081
  }
11794
- const subResourceId = (0, import_types45.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
12082
+ const subResourceId = (0, import_types46.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
11795
12083
  if (graph.hasNode(subResourceId)) {
11796
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types45.EdgeType.CALLS };
12084
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
11797
12085
  }
11798
- const bareResourceId = (0, import_types45.infraId)(signal.targetKind, signal.targetName);
12086
+ const bareResourceId = (0, import_types46.infraId)(signal.targetKind, signal.targetName);
11799
12087
  if (graph.hasNode(bareResourceId)) {
11800
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types45.EdgeType.CALLS };
12088
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
11801
12089
  }
11802
- const projectLevelId = (0, import_types45.infraId)("supabase", config.nodeRef);
12090
+ const projectLevelId = (0, import_types46.infraId)("supabase", config.nodeRef);
11803
12091
  if (graph.hasNode(projectLevelId)) {
11804
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types45.EdgeType.CALLS };
12092
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
11805
12093
  }
11806
12094
  return null;
11807
12095
  };
@@ -11894,7 +12182,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
11894
12182
 
11895
12183
  // src/connectors/railway/index.ts
11896
12184
  init_cjs_shims();
11897
- var import_types49 = require("@neat.is/types");
12185
+ var import_types50 = require("@neat.is/types");
11898
12186
 
11899
12187
  // src/connectors/railway/client.ts
11900
12188
  init_cjs_shims();
@@ -12045,7 +12333,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
12045
12333
  const out = [];
12046
12334
  graph.forEachNode((_id, attrs) => {
12047
12335
  const node = attrs;
12048
- if (node.type !== import_types49.NodeType.RouteNode) return;
12336
+ if (node.type !== import_types50.NodeType.RouteNode) return;
12049
12337
  const route = attrs;
12050
12338
  if (route.service !== serviceName) return;
12051
12339
  out.push({
@@ -12149,12 +12437,12 @@ function createRailwayResolveTarget(config) {
12149
12437
  const serviceName = config.serviceNameById[config.serviceId];
12150
12438
  if (!serviceName) return null;
12151
12439
  if (signal.targetKind === ROUTE_TARGET_KIND) {
12152
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types49.EdgeType.CALLS };
12440
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types50.EdgeType.CALLS };
12153
12441
  }
12154
12442
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
12155
12443
  const peerName = config.serviceNameById[signal.targetName];
12156
12444
  if (!peerName) return null;
12157
- return { targetNodeId: (0, import_types49.serviceId)(peerName), serviceName, edgeType: import_types49.EdgeType.CONNECTS_TO };
12445
+ return { targetNodeId: (0, import_types50.serviceId)(peerName), serviceName, edgeType: import_types50.EdgeType.CONNECTS_TO };
12158
12446
  }
12159
12447
  return null;
12160
12448
  };
@@ -12342,7 +12630,7 @@ function mapLogEntriesToSignals(entries) {
12342
12630
 
12343
12631
  // src/connectors/firebase/resolve.ts
12344
12632
  init_cjs_shims();
12345
- var import_types50 = require("@neat.is/types");
12633
+ var import_types51 = require("@neat.is/types");
12346
12634
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
12347
12635
  switch (resourceType) {
12348
12636
  case "cloud_function":
@@ -12357,7 +12645,7 @@ function routeEntriesFor(graph, serviceName) {
12357
12645
  const entries = [];
12358
12646
  graph.forEachNode((_id, attrs) => {
12359
12647
  const node = attrs;
12360
- if (node.type !== import_types50.NodeType.RouteNode) return;
12648
+ if (node.type !== import_types51.NodeType.RouteNode) return;
12361
12649
  const route = attrs;
12362
12650
  if (route.service !== serviceName) return;
12363
12651
  entries.push({
@@ -12389,7 +12677,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
12389
12677
  return {
12390
12678
  targetNodeId: match.routeNodeId,
12391
12679
  serviceName,
12392
- edgeType: import_types50.EdgeType.CALLS
12680
+ edgeType: import_types51.EdgeType.CALLS
12393
12681
  };
12394
12682
  };
12395
12683
  }
@@ -12416,7 +12704,7 @@ init_cjs_shims();
12416
12704
 
12417
12705
  // src/connectors/cloudflare/connector.ts
12418
12706
  init_cjs_shims();
12419
- var import_types52 = require("@neat.is/types");
12707
+ var import_types53 = require("@neat.is/types");
12420
12708
 
12421
12709
  // src/connectors/cloudflare/client.ts
12422
12710
  init_cjs_shims();
@@ -12580,7 +12868,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
12580
12868
  graph.forEachNode((id, attrs) => {
12581
12869
  if (found) return;
12582
12870
  const a = attrs;
12583
- if (a.type === import_types52.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
12871
+ if (a.type === import_types53.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
12584
12872
  found = id;
12585
12873
  }
12586
12874
  });
@@ -12592,7 +12880,7 @@ function findMatchingRouteNode(graph, serviceName, method, path60) {
12592
12880
  graph.forEachNode((id, attrs) => {
12593
12881
  if (found) return;
12594
12882
  const a = attrs;
12595
- if (a.type !== import_types52.NodeType.RouteNode || a.service !== serviceName) return;
12883
+ if (a.type !== import_types53.NodeType.RouteNode || a.service !== serviceName) return;
12596
12884
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
12597
12885
  const routeMethod = (a.method ?? "").toUpperCase();
12598
12886
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -12611,11 +12899,11 @@ function createCloudflareResolveTarget(config, graph) {
12611
12899
  };
12612
12900
  const mapping = config.workers?.[scriptName];
12613
12901
  if (mapping) {
12614
- const wholeFileId = (0, import_types52.fileId)(mapping.service, mapping.entryFile);
12902
+ const wholeFileId = (0, import_types53.fileId)(mapping.service, mapping.entryFile);
12615
12903
  return {
12616
12904
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
12617
12905
  serviceName: mapping.service,
12618
- edgeType: import_types52.EdgeType.CALLS
12906
+ edgeType: import_types53.EdgeType.CALLS
12619
12907
  };
12620
12908
  }
12621
12909
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -12624,13 +12912,13 @@ function createCloudflareResolveTarget(config, graph) {
12624
12912
  return {
12625
12913
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
12626
12914
  serviceName: fileNode.service,
12627
- edgeType: import_types52.EdgeType.CALLS
12915
+ edgeType: import_types53.EdgeType.CALLS
12628
12916
  };
12629
12917
  }
12630
12918
  return {
12631
- targetNodeId: (0, import_types52.infraId)("cloudflare-worker", scriptName),
12919
+ targetNodeId: (0, import_types53.infraId)("cloudflare-worker", scriptName),
12632
12920
  serviceName: scriptName,
12633
- edgeType: import_types52.EdgeType.CALLS,
12921
+ edgeType: import_types53.EdgeType.CALLS,
12634
12922
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
12635
12923
  };
12636
12924
  };
@@ -12826,14 +13114,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
12826
13114
 
12827
13115
  // src/connectors/neon/resolve.ts
12828
13116
  init_cjs_shims();
12829
- var import_types56 = require("@neat.is/types");
13117
+ var import_types57 = require("@neat.is/types");
12830
13118
  function createNeonResolveTarget(config) {
12831
13119
  return (signal) => {
12832
13120
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
12833
13121
  return {
12834
- targetNodeId: (0, import_types56.infraId)("sql-table", signal.targetName),
13122
+ targetNodeId: (0, import_types57.infraId)("sql-table", signal.targetName),
12835
13123
  serviceName: config.serviceName,
12836
- edgeType: import_types56.EdgeType.CALLS,
13124
+ edgeType: import_types57.EdgeType.CALLS,
12837
13125
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
12838
13126
  };
12839
13127
  };
@@ -12871,6 +13159,412 @@ function createNeonConnector(config, deps = {}) {
12871
13159
  };
12872
13160
  }
12873
13161
 
13162
+ // src/connectors/cloud-run/index.ts
13163
+ init_cjs_shims();
13164
+
13165
+ // src/connectors/cloud-run/client.ts
13166
+ init_cjs_shims();
13167
+ function cloudRunRequestLogName(projectId) {
13168
+ return `projects/${projectId}/logs/run.googleapis.com%2Frequests`;
13169
+ }
13170
+ function buildCloudRunEntriesFilter(projectId, sinceIso) {
13171
+ return [
13172
+ `logName = "${cloudRunRequestLogName(projectId)}"`,
13173
+ `resource.type = "${CLOUD_RUN_RESOURCE_TYPE}"`,
13174
+ 'httpRequest.requestMethod != ""',
13175
+ `timestamp >= "${sinceIso}"`
13176
+ ].join(" AND ");
13177
+ }
13178
+ var CLOUD_RUN_RESOURCE_TYPE = "cloud_run_revision";
13179
+ var DEFAULT_LOOKBACK_MS2 = 24 * 60 * 60 * 1e3;
13180
+ var ENTRIES_LIST_URL2 = "https://logging.googleapis.com/v2/entries:list";
13181
+ var PAGE_SIZE2 = 1e3;
13182
+ var MAX_PAGES2 = 20;
13183
+ async function fetchCloudRunRequestLogEntries(creds, sinceIso, apiUrl = ENTRIES_LIST_URL2) {
13184
+ const filter = buildCloudRunEntriesFilter(creds.projectId, sinceIso);
13185
+ const out = [];
13186
+ let pageToken;
13187
+ for (let page = 0; page < MAX_PAGES2; page++) {
13188
+ const body = {
13189
+ resourceNames: [`projects/${creds.projectId}`],
13190
+ filter,
13191
+ orderBy: "timestamp asc",
13192
+ pageSize: PAGE_SIZE2,
13193
+ ...pageToken ? { pageToken } : {}
13194
+ };
13195
+ const res = await junctionFetch(
13196
+ apiUrl,
13197
+ {
13198
+ method: "POST",
13199
+ headers: {
13200
+ ...bearerAuthHeader(creds.accessToken),
13201
+ "Content-Type": "application/json"
13202
+ },
13203
+ body: JSON.stringify(body)
13204
+ },
13205
+ // accountKey: the GCP project id — one customer's Cloud Logging quota is
13206
+ // scoped per GCP project (ADR-131's per-(provider, accountKey) rate-limit
13207
+ // bucket), the same key Firebase's connector uses.
13208
+ { provider: "cloud-run", accountKey: creds.projectId }
13209
+ );
13210
+ if (!res.ok) {
13211
+ throw new Error(`Cloud Logging entries.list failed: ${res.status} ${res.statusText}`);
13212
+ }
13213
+ const json = await res.json();
13214
+ if (Array.isArray(json.entries)) out.push(...json.entries);
13215
+ if (!json.nextPageToken) break;
13216
+ pageToken = json.nextPageToken;
13217
+ }
13218
+ return out;
13219
+ }
13220
+
13221
+ // src/connectors/cloud-run/map.ts
13222
+ init_cjs_shims();
13223
+
13224
+ // src/connectors/cloud-run/types.ts
13225
+ init_cjs_shims();
13226
+ function readCloudRunCredentials(raw) {
13227
+ const projectId = raw["projectId"];
13228
+ const accessToken = raw["accessToken"];
13229
+ if (typeof projectId !== "string" || projectId.length === 0) {
13230
+ throw new Error("cloud-run connector: credentials.projectId must be a non-empty string");
13231
+ }
13232
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
13233
+ throw new Error("cloud-run connector: credentials.accessToken must be a non-empty string");
13234
+ }
13235
+ return { projectId, accessToken };
13236
+ }
13237
+ var CLOUD_RUN_TARGET_KIND = "cloud_run_revision";
13238
+ var FIELD_SEP2 = "\0";
13239
+ function packCloudRunTargetName(identity) {
13240
+ return [identity.serviceName, identity.method, identity.path].join(FIELD_SEP2);
13241
+ }
13242
+ function parseCloudRunTargetName(targetName) {
13243
+ const firstSep = targetName.indexOf(FIELD_SEP2);
13244
+ if (firstSep === -1) return null;
13245
+ const serviceName = targetName.slice(0, firstSep);
13246
+ const rest = targetName.slice(firstSep + 1);
13247
+ const secondSep = rest.indexOf(FIELD_SEP2);
13248
+ if (secondSep === -1) return null;
13249
+ const method = rest.slice(0, secondSep);
13250
+ const path60 = rest.slice(secondSep + 1);
13251
+ if (!serviceName || !method || !path60) return null;
13252
+ return { serviceName, method, path: path60 };
13253
+ }
13254
+
13255
+ // src/connectors/cloud-run/map.ts
13256
+ var CLOUD_RUN_RESOURCE_TYPE2 = "cloud_run_revision";
13257
+ function pathFromRequestUrl2(requestUrl) {
13258
+ if (typeof requestUrl !== "string" || requestUrl.length === 0) return null;
13259
+ if (requestUrl.startsWith("/")) {
13260
+ const withoutQuery = requestUrl.split("?")[0];
13261
+ return withoutQuery && withoutQuery.length > 0 ? withoutQuery : "/";
13262
+ }
13263
+ try {
13264
+ const candidate = requestUrl.startsWith("//") ? `https:${requestUrl}` : requestUrl;
13265
+ const parsed = new URL(candidate);
13266
+ return parsed.pathname || "/";
13267
+ } catch {
13268
+ return null;
13269
+ }
13270
+ }
13271
+ var ERROR_STATUS_THRESHOLD4 = 500;
13272
+ function mapLogEntryToSignal2(entry) {
13273
+ if (!entry || typeof entry !== "object") return null;
13274
+ if (entry.resource?.type !== CLOUD_RUN_RESOURCE_TYPE2) return null;
13275
+ const serviceName = entry.resource?.labels?.["service_name"];
13276
+ if (typeof serviceName !== "string" || serviceName.length === 0) return null;
13277
+ const req = entry.httpRequest;
13278
+ if (!req) return null;
13279
+ if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
13280
+ const method = req.requestMethod.toUpperCase();
13281
+ const path60 = pathFromRequestUrl2(req.requestUrl);
13282
+ if (path60 === null) return null;
13283
+ const timestamp = entry.timestamp;
13284
+ if (typeof timestamp !== "string" || timestamp.length === 0) return null;
13285
+ const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
13286
+ return {
13287
+ targetKind: CLOUD_RUN_TARGET_KIND,
13288
+ targetName: packCloudRunTargetName({ serviceName, method, path: path60 }),
13289
+ callCount: 1,
13290
+ errorCount: isError ? 1 : 0,
13291
+ lastObservedIso: timestamp
13292
+ };
13293
+ }
13294
+ function mapLogEntriesToSignals2(entries) {
13295
+ const out = [];
13296
+ for (const entry of entries) {
13297
+ const signal = mapLogEntryToSignal2(entry);
13298
+ if (signal) out.push(signal);
13299
+ }
13300
+ return out;
13301
+ }
13302
+
13303
+ // src/connectors/cloud-run/resolve.ts
13304
+ init_cjs_shims();
13305
+ var import_types61 = require("@neat.is/types");
13306
+ var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
13307
+ function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
13308
+ let found = null;
13309
+ graph.forEachNode((_id, attrs) => {
13310
+ if (found) return;
13311
+ const node = attrs;
13312
+ if (node.type !== import_types61.NodeType.RouteNode) return;
13313
+ const route = attrs;
13314
+ if (route.service !== serviceName || !route.pathTemplate) return;
13315
+ if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
13316
+ const routeMethod = route.method.toUpperCase();
13317
+ if (routeMethod !== "ALL" && routeMethod !== method) return;
13318
+ found = route.id;
13319
+ });
13320
+ return found;
13321
+ }
13322
+ function createCloudRunResolveTarget(graph, config) {
13323
+ return (signal) => {
13324
+ if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
13325
+ const identity = parseCloudRunTargetName(signal.targetName);
13326
+ if (!identity) return null;
13327
+ const { serviceName: gcpServiceName, method, path: path60 } = identity;
13328
+ const mappedService = config.serviceMap?.[gcpServiceName];
13329
+ if (mappedService) {
13330
+ const routeNodeId = findMatchingRouteNode2(
13331
+ graph,
13332
+ mappedService,
13333
+ method,
13334
+ normalizePathTemplate(path60)
13335
+ );
13336
+ if (routeNodeId) {
13337
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types61.EdgeType.CALLS };
13338
+ }
13339
+ }
13340
+ return {
13341
+ targetNodeId: (0, import_types61.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
13342
+ serviceName: mappedService ?? gcpServiceName,
13343
+ edgeType: import_types61.EdgeType.CALLS,
13344
+ ensureInfraNode: {
13345
+ kind: CLOUD_RUN_SERVICE_INFRA_KIND,
13346
+ name: gcpServiceName,
13347
+ provider: "cloud-run"
13348
+ }
13349
+ };
13350
+ };
13351
+ }
13352
+
13353
+ // src/connectors/cloud-run/index.ts
13354
+ var CloudRunConnector = class {
13355
+ constructor(config = {}) {
13356
+ this.config = config;
13357
+ }
13358
+ config;
13359
+ provider = "cloud-run";
13360
+ async poll(ctx) {
13361
+ const creds = readCloudRunCredentials(ctx.credentials);
13362
+ const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_LOOKBACK_MS2;
13363
+ const sinceIso = boundedSinceIso(ctx.since, /* @__PURE__ */ new Date(), maxLookbackMs);
13364
+ const entries = await fetchCloudRunRequestLogEntries(creds, sinceIso, this.config.apiUrl);
13365
+ return mapLogEntriesToSignals2(entries);
13366
+ }
13367
+ };
13368
+ function boundedSinceIso(since, now, maxLookbackMs) {
13369
+ const floor = new Date(now.getTime() - maxLookbackMs);
13370
+ if (!since) return floor.toISOString();
13371
+ const sinceMs = new Date(since).getTime();
13372
+ if (Number.isNaN(sinceMs)) return floor.toISOString();
13373
+ return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
13374
+ }
13375
+ function createCloudRunConnector(graph, config = {}) {
13376
+ return {
13377
+ connector: new CloudRunConnector(config),
13378
+ resolveTarget: createCloudRunResolveTarget(graph, config)
13379
+ };
13380
+ }
13381
+
13382
+ // src/connectors/render/index.ts
13383
+ init_cjs_shims();
13384
+ var import_types64 = require("@neat.is/types");
13385
+
13386
+ // src/connectors/render/types.ts
13387
+ init_cjs_shims();
13388
+ function readRenderToken(credentials) {
13389
+ const token = credentials.token;
13390
+ if (typeof token !== "string" || token.length === 0) {
13391
+ throw new Error("Render connector requires ctx.credentials.token (a Render API key)");
13392
+ }
13393
+ return token;
13394
+ }
13395
+ function renderLabelValue(entry, name) {
13396
+ if (!Array.isArray(entry.labels)) return void 0;
13397
+ const label = entry.labels.find((l) => l && typeof l === "object" && l.name === name);
13398
+ return label && typeof label.value === "string" ? label.value : void 0;
13399
+ }
13400
+
13401
+ // src/connectors/render/client.ts
13402
+ init_cjs_shims();
13403
+ var DEFAULT_RENDER_API_URL = "https://api.render.com/v1";
13404
+ var DEFAULT_RENDER_LOG_LIMIT = 100;
13405
+ var RENDER_MAX_LOG_LIMIT = 100;
13406
+ var DEFAULT_RENDER_MAX_PAGES = 20;
13407
+ var DEFAULT_MAX_LOOKBACK_MS4 = 24 * 60 * 60 * 1e3;
13408
+ function clampLimit(limit) {
13409
+ const raw = Math.trunc(limit ?? DEFAULT_RENDER_LOG_LIMIT);
13410
+ if (!Number.isFinite(raw) || raw < 1) return DEFAULT_RENDER_LOG_LIMIT;
13411
+ return Math.min(raw, RENDER_MAX_LOG_LIMIT);
13412
+ }
13413
+ function boundedRenderStartTime(since, now, maxLookbackMs) {
13414
+ const floor = new Date(now.getTime() - maxLookbackMs);
13415
+ if (!since) return floor.toISOString();
13416
+ const sinceMs = new Date(since).getTime();
13417
+ if (Number.isNaN(sinceMs)) return floor.toISOString();
13418
+ return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
13419
+ }
13420
+ async function fetchRenderLogPage(config, token, startTime, endTime, limit, fetchImpl) {
13421
+ const url = new URL(`${config.apiUrl ?? DEFAULT_RENDER_API_URL}/logs`);
13422
+ url.searchParams.set("ownerId", config.ownerId);
13423
+ url.searchParams.set("resource", config.resourceId);
13424
+ url.searchParams.set("type", "request");
13425
+ url.searchParams.set("startTime", startTime);
13426
+ url.searchParams.set("endTime", endTime);
13427
+ url.searchParams.set("direction", "backward");
13428
+ url.searchParams.set("limit", String(limit));
13429
+ const res = await junctionFetch(
13430
+ url,
13431
+ { method: "GET", headers: { ...bearerAuthHeader(token) } },
13432
+ { provider: "render", accountKey: config.ownerId, ...fetchImpl ? { fetchImpl } : {} }
13433
+ );
13434
+ if (!res.ok) {
13435
+ throw new Error(`Render logs request failed: ${res.status} ${res.statusText}`);
13436
+ }
13437
+ return await res.json();
13438
+ }
13439
+ async function fetchRenderRequestLogs(config, token, startTime, endTime, fetchImpl) {
13440
+ const limit = clampLimit(config.limit);
13441
+ const maxPages = Math.max(1, Math.trunc(config.maxPages ?? DEFAULT_RENDER_MAX_PAGES));
13442
+ const out = [];
13443
+ let pageStart = startTime;
13444
+ let pageEnd = endTime;
13445
+ for (let page = 0; page < maxPages; page++) {
13446
+ const body = await fetchRenderLogPage(config, token, pageStart, pageEnd, limit, fetchImpl);
13447
+ if (Array.isArray(body.logs)) out.push(...body.logs);
13448
+ if (!body.hasMore || !body.nextStartTime || !body.nextEndTime) break;
13449
+ pageStart = body.nextStartTime;
13450
+ pageEnd = body.nextEndTime;
13451
+ }
13452
+ return out;
13453
+ }
13454
+
13455
+ // src/connectors/render/index.ts
13456
+ var ROUTE_TARGET_KIND2 = "route";
13457
+ var UNMATCHED_ROUTE_TARGET_KIND2 = "unmatched-route";
13458
+ function buildRenderRouteIndex(graph, serviceName) {
13459
+ const out = [];
13460
+ graph.forEachNode((_id, attrs) => {
13461
+ const node = attrs;
13462
+ if (node.type !== import_types64.NodeType.RouteNode) return;
13463
+ const route = attrs;
13464
+ if (route.service !== serviceName) return;
13465
+ out.push({
13466
+ method: route.method.toUpperCase(),
13467
+ normalizedPath: normalizePathTemplate(route.pathTemplate),
13468
+ routeNodeId: route.id,
13469
+ path: route.path,
13470
+ line: route.line
13471
+ });
13472
+ });
13473
+ return out;
13474
+ }
13475
+ function findRenderRoute(entries, method, normalizedPath) {
13476
+ return entries.find(
13477
+ (e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || e.method === method)
13478
+ );
13479
+ }
13480
+ function bucketKey3(method, normalizedPath) {
13481
+ return `${method} ${normalizedPath}`;
13482
+ }
13483
+ function isHttpErrorStatus2(status2) {
13484
+ return status2 >= 400;
13485
+ }
13486
+ function upsertBucket2(buckets2, key, isError, timestamp, build) {
13487
+ const existing = buckets2.get(key);
13488
+ if (existing) {
13489
+ existing.callCount += 1;
13490
+ if (isError) existing.errorCount += 1;
13491
+ if (timestamp > existing.lastObservedIso) existing.lastObservedIso = timestamp;
13492
+ return;
13493
+ }
13494
+ buckets2.set(key, { callCount: 1, errorCount: isError ? 1 : 0, lastObservedIso: timestamp, ...build() });
13495
+ }
13496
+ function mapRenderRequestLogsToSignals(entries, routeIndex) {
13497
+ const buckets2 = /* @__PURE__ */ new Map();
13498
+ if (!Array.isArray(entries)) return [];
13499
+ for (const entry of entries) {
13500
+ if (!entry || typeof entry !== "object") continue;
13501
+ if (typeof entry.timestamp !== "string") continue;
13502
+ const method = renderLabelValue(entry, "method");
13503
+ const rawPath = renderLabelValue(entry, "path");
13504
+ if (typeof method !== "string" || method.length === 0) continue;
13505
+ if (typeof rawPath !== "string" || rawPath.length === 0) continue;
13506
+ const methodUpper = method.toUpperCase();
13507
+ const pathOnly = rawPath.split("?")[0];
13508
+ const normalizedPath = normalizePathTemplate(pathOnly);
13509
+ const statusCode = Number.parseInt(renderLabelValue(entry, "statusCode") ?? "", 10);
13510
+ const isError = Number.isFinite(statusCode) && isHttpErrorStatus2(statusCode);
13511
+ const match = findRenderRoute(routeIndex, methodUpper, normalizedPath);
13512
+ if (match) {
13513
+ upsertBucket2(buckets2, `route:${match.routeNodeId}`, isError, entry.timestamp, () => ({
13514
+ targetKind: ROUTE_TARGET_KIND2,
13515
+ targetName: match.routeNodeId,
13516
+ // RouteNode.line is optional in the schema (packages/types/src/
13517
+ // nodes.ts) even though routes.ts always sets it today — skip the
13518
+ // callSite rather than fabricate a line when it's ever absent
13519
+ // (file-awareness.md §6).
13520
+ ...match.line !== void 0 ? { callSite: { file: match.path, line: match.line } } : {}
13521
+ }));
13522
+ } else {
13523
+ upsertBucket2(
13524
+ buckets2,
13525
+ `unmatched:${bucketKey3(methodUpper, normalizedPath)}`,
13526
+ isError,
13527
+ entry.timestamp,
13528
+ () => ({
13529
+ targetKind: UNMATCHED_ROUTE_TARGET_KIND2,
13530
+ targetName: bucketKey3(methodUpper, normalizedPath)
13531
+ })
13532
+ );
13533
+ }
13534
+ }
13535
+ return [...buckets2.values()].map((b) => ({
13536
+ targetKind: b.targetKind,
13537
+ targetName: b.targetName,
13538
+ callCount: b.callCount,
13539
+ errorCount: b.errorCount,
13540
+ lastObservedIso: b.lastObservedIso,
13541
+ ...b.callSite ? { callSite: b.callSite } : {}
13542
+ }));
13543
+ }
13544
+ function createRenderResolveTarget(config) {
13545
+ return (signal) => {
13546
+ if (signal.targetKind === ROUTE_TARGET_KIND2) {
13547
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
13548
+ }
13549
+ return null;
13550
+ };
13551
+ }
13552
+ function createRenderConnector(graph, config) {
13553
+ return {
13554
+ provider: "render",
13555
+ async poll(ctx) {
13556
+ const token = readRenderToken(ctx.credentials);
13557
+ const now = /* @__PURE__ */ new Date();
13558
+ const maxLookbackMs = config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS4;
13559
+ const startTime = boundedRenderStartTime(ctx.since, now, maxLookbackMs);
13560
+ const endTime = now.toISOString();
13561
+ const logs = await fetchRenderRequestLogs(config, token, startTime, endTime);
13562
+ const routeIndex = buildRenderRouteIndex(graph, config.serviceName);
13563
+ return mapRenderRequestLogsToSignals(logs, routeIndex);
13564
+ }
13565
+ };
13566
+ }
13567
+
12874
13568
  // src/connectors/registry.ts
12875
13569
  var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
12876
13570
  async function authProbe(input) {
@@ -13046,6 +13740,71 @@ var PROVIDER_DISPATCH = {
13046
13740
  return { ok: false, reason: `neon telemetry read failed: ${err.message}` };
13047
13741
  }
13048
13742
  }
13743
+ },
13744
+ "cloud-run": {
13745
+ provider: "cloud-run",
13746
+ // Cloud Run reads both projectId and accessToken from the credential; the
13747
+ // single-string form maps to the secret (the token), and the required-fields
13748
+ // check below catches a projectId that was never supplied.
13749
+ primaryCredentialKey: "accessToken",
13750
+ requiredCredentialFields: ["projectId", "accessToken"],
13751
+ requiredOptionFields: [],
13752
+ build(graph, options) {
13753
+ return createCloudRunConnector(graph, options);
13754
+ },
13755
+ // POST entries:list with pageSize 1 — the exact surface poll() reads, so the
13756
+ // probe checks the actual `logging.logEntries.list` permission the connector
13757
+ // needs. A GET on the lighter logs.list endpoint (as Firebase probes) would
13758
+ // instead check `logging.logs.list`, falsely rejecting a correctly-scoped
13759
+ // custom role that carries only `logging.logEntries.list` (the narrowest
13760
+ // grant docs/connectors/cloud-run.md documents) — the same false-negative
13761
+ // trap Railway's validate avoids by probing its real query. A 2xx means the
13762
+ // token can list log entries; 401/403 means the provider rejected it.
13763
+ validate({ credentials, fetchImpl }) {
13764
+ const projectId = String(credentials.projectId ?? "");
13765
+ return authProbe({
13766
+ provider: "cloud-run",
13767
+ accountKey: projectId || "validate",
13768
+ url: "https://logging.googleapis.com/v2/entries:list",
13769
+ token: String(credentials.accessToken ?? ""),
13770
+ init: {
13771
+ method: "POST",
13772
+ headers: { "Content-Type": "application/json" },
13773
+ body: JSON.stringify({ resourceNames: [`projects/${projectId}`], pageSize: 1 })
13774
+ },
13775
+ ...fetchImpl ? { fetchImpl } : {}
13776
+ });
13777
+ }
13778
+ },
13779
+ render: {
13780
+ provider: "render",
13781
+ primaryCredentialKey: "token",
13782
+ requiredCredentialFields: ["token"],
13783
+ requiredOptionFields: ["ownerId", "resourceId", "serviceName"],
13784
+ build(graph, options) {
13785
+ const config = options;
13786
+ return {
13787
+ connector: createRenderConnector(graph, config),
13788
+ resolveTarget: createRenderResolveTarget(config)
13789
+ };
13790
+ },
13791
+ // GET /v1/services?limit=1 — the cheapest read the Render API key
13792
+ // authenticates against (render.com/docs/api). Unlike Railway's GraphQL
13793
+ // gateway, Render is a plain REST API: a live key returns 2xx, a bad one a
13794
+ // 401/403, so authProbe's status-code check is a true verdict here. The
13795
+ // logs query itself also needs an ownerId + resource; `services` needs
13796
+ // neither and still fails 401 on a bad token, so it's the honest probe.
13797
+ validate({ credentials, options, fetchImpl }) {
13798
+ const cfg = options;
13799
+ const baseUrl = cfg.apiUrl ?? DEFAULT_RENDER_API_URL;
13800
+ return authProbe({
13801
+ provider: "render",
13802
+ accountKey: cfg.ownerId ?? "validate",
13803
+ url: `${baseUrl}/services?limit=1`,
13804
+ token: String(credentials.token ?? ""),
13805
+ ...fetchImpl ? { fetchImpl } : {}
13806
+ });
13807
+ }
13049
13808
  }
13050
13809
  };
13051
13810
  function vercelCredsFrom(credentials) {
@@ -13355,11 +14114,11 @@ function registerRoutes(scope, ctx) {
13355
14114
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
13356
14115
  const parsed = [];
13357
14116
  for (const c of candidates) {
13358
- const r = import_types59.DivergenceTypeSchema.safeParse(c);
14117
+ const r = import_types66.DivergenceTypeSchema.safeParse(c);
13359
14118
  if (!r.success) {
13360
14119
  return reply.code(400).send({
13361
14120
  error: `unknown divergence type "${c}"`,
13362
- allowed: import_types59.DivergenceTypeSchema.options
14121
+ allowed: import_types66.DivergenceTypeSchema.options
13363
14122
  });
13364
14123
  }
13365
14124
  parsed.push(r.data);
@@ -13668,7 +14427,7 @@ function registerRoutes(scope, ctx) {
13668
14427
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
13669
14428
  let violations = await log.readAll();
13670
14429
  if (req.query.severity) {
13671
- const sev = import_types59.PolicySeveritySchema.safeParse(req.query.severity);
14430
+ const sev = import_types66.PolicySeveritySchema.safeParse(req.query.severity);
13672
14431
  if (!sev.success) {
13673
14432
  return reply.code(400).send({
13674
14433
  error: "invalid severity",
@@ -13707,7 +14466,7 @@ function registerRoutes(scope, ctx) {
13707
14466
  scope.post("/policies/check", async (req, reply) => {
13708
14467
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
13709
14468
  if (!proj) return;
13710
- const parsed = import_types59.PoliciesCheckBodySchema.safeParse(req.body ?? {});
14469
+ const parsed = import_types66.PoliciesCheckBodySchema.safeParse(req.body ?? {});
13711
14470
  if (!parsed.success) {
13712
14471
  return reply.code(400).send({
13713
14472
  error: "invalid /policies/check body",