@neat.is/core 0.9.21-dev.20260923 → 0.9.21

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.
@@ -17474,7 +17474,7 @@ var INTENT_RULES = [
17474
17474
  },
17475
17475
  {
17476
17476
  intent: "blast-radius",
17477
- test: /\b(blast|break[\s-]?if|breaks[\s-]?if|impact|downstream|dependents?|redeploy|who\s+(?:uses|calls|depends)|what\s+depends\s+on|affect(?:s|ed)?)\b/
17477
+ test: /\b(blast|break[\s-]?if|breaks[\s-]?if|impact|downstream|dependents?|redeploy|who\s+(?:uses|calls|depends(?:\s+on)?)|what\s+depends\s+on|consumers?\s+of|callers?\s+of|affect(?:s|ed)?)\b/
17478
17478
  },
17479
17479
  {
17480
17480
  intent: "divergence",
@@ -17486,11 +17486,11 @@ var INTENT_RULES = [
17486
17486
  },
17487
17487
  {
17488
17488
  intent: "observed",
17489
- test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
17489
+ test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually|really\s+call\w*|observed|runtime\s+traffic|slow|latency|p95|timing)\b/
17490
17490
  },
17491
17491
  {
17492
17492
  intent: "dependencies",
17493
- test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
17493
+ test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?|talks?\s+to|connects?\s+to|hits?|reads?\s+from|writes?\s+to)\b/
17494
17494
  }
17495
17495
  ];
17496
17496
  function classifyIntent(question) {
@@ -17657,7 +17657,25 @@ var INTENT_WORDS = /* @__PURE__ */ new Set([
17657
17657
  "rely",
17658
17658
  "needs",
17659
17659
  "need",
17660
- "sync"
17660
+ "sync",
17661
+ "talk",
17662
+ "talks",
17663
+ "connect",
17664
+ "connects",
17665
+ "hit",
17666
+ "hits",
17667
+ "read",
17668
+ "reads",
17669
+ "write",
17670
+ "writes",
17671
+ "consumer",
17672
+ "consumers",
17673
+ "caller",
17674
+ "callers",
17675
+ "slow",
17676
+ "latency",
17677
+ "p95",
17678
+ "timing"
17661
17679
  ]);
17662
17680
  function tokens(input) {
17663
17681
  const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
@@ -17681,6 +17699,13 @@ function idBody(id) {
17681
17699
  const colon = id.indexOf(":");
17682
17700
  return colon >= 0 ? id.slice(colon + 1) : id;
17683
17701
  }
17702
+ var TYPE_NOUNS = [
17703
+ { nouns: ["database", "db", "table"], type: NodeType32.DatabaseNode },
17704
+ { nouns: ["service", "api", "backend"], type: NodeType32.ServiceNode },
17705
+ { nouns: ["route", "endpoint"], type: NodeType32.RouteNode },
17706
+ { nouns: ["file"], type: NodeType32.FileNode },
17707
+ { nouns: ["config"], type: NodeType32.ConfigNode }
17708
+ ];
17684
17709
  var EMBED_MIN_SCORE = 0.35;
17685
17710
  async function resolveEntities(graph, question, searchIndex, maxNodes) {
17686
17711
  const qTokens = queryTokens(question);
@@ -17688,6 +17713,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17688
17713
  const best = /* @__PURE__ */ new Map();
17689
17714
  const consider = (cand) => {
17690
17715
  const cur = best.get(cand.nodeId);
17716
+ if (cur?.via === "id" && cand.via === "embedding") return;
17691
17717
  if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
17692
17718
  };
17693
17719
  graph.forEachNode((id, attrs) => {
@@ -17697,12 +17723,12 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17697
17723
  const body = idBody(id);
17698
17724
  const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
17699
17725
  if (labelTokens.size === 0) return;
17700
- let matched = 0;
17701
- for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
17726
+ let matched2 = 0;
17727
+ for (const t of qTokens) if (labelTokens.has(t)) matched2 += 1;
17702
17728
  const named = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
17703
- if (matched === 0 && !named) return;
17704
- const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
17705
- const via = named ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
17729
+ if (matched2 === 0 && !named) return;
17730
+ const coverage = qTokens.length > 0 ? matched2 / qTokens.length : 0;
17731
+ const via = named ? "id" : matched2 > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
17706
17732
  const score = named ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
17707
17733
  consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
17708
17734
  });
@@ -17712,6 +17738,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17712
17738
  if (res.provider !== "substring") {
17713
17739
  for (const m of res.matches) {
17714
17740
  if (m.node.type === NodeType32.FrontierNode) continue;
17741
+ if (!graph.hasNode(m.node.id)) continue;
17715
17742
  const already = best.get(m.node.id);
17716
17743
  if (!already && m.score < EMBED_MIN_SCORE) continue;
17717
17744
  consider({
@@ -17725,10 +17752,31 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17725
17752
  } catch {
17726
17753
  }
17727
17754
  }
17728
- const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
17729
- return [...best.values()].sort(
17730
- (a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
17755
+ if (best.size === 0) {
17756
+ const words = new Set(tokens(question));
17757
+ const kind = TYPE_NOUNS.find((entry) => entry.nouns.some((noun) => words.has(noun)));
17758
+ if (kind) {
17759
+ const ids = graph.filterNodes((_id, attrs) => attrs.type === kind.type).sort();
17760
+ if (ids.length > 1) {
17761
+ return { matched: [], ambiguousType: { noun: kind.nouns.find((noun) => words.has(noun)), ids } };
17762
+ }
17763
+ if (ids.length === 1) {
17764
+ const id = ids[0];
17765
+ return { matched: [{ nodeId: id, label: nodeName(graph.getNodeAttributes(id)), via: "type", score: 0.6 }].slice(0, maxNodes) };
17766
+ }
17767
+ return { matched: [] };
17768
+ }
17769
+ }
17770
+ const viaRank = { id: 4, label: 3, token: 2, type: 1, embedding: 0 };
17771
+ const ranked = [...best.values()].sort(
17772
+ (a, b) => Number(b.via === "id") - Number(a.via === "id") || b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
17773
+ );
17774
+ const primary = ranked[0];
17775
+ const primaryType = primary && graph.getNodeAttributes(primary.nodeId).type;
17776
+ const matched = ranked.filter(
17777
+ (candidate) => !primary || primary.via !== "id" || primary.score < 0.85 || candidate === primary || graph.getNodeAttributes(candidate.nodeId).type === primaryType || candidate.via !== "embedding" && candidate.score >= primary.score - 0.2
17731
17778
  ).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
17779
+ return { matched };
17732
17780
  }
17733
17781
  function incidentsForNode(nodeId, incidents) {
17734
17782
  if (!incidents || incidents.length === 0) return [];
@@ -17820,6 +17868,23 @@ function buildBlastSection(graph, node) {
17820
17868
  facts
17821
17869
  };
17822
17870
  }
17871
+ function buildDatabaseCallersSection(graph, node) {
17872
+ const callers = getBlastRadius(graph, node).affectedNodes.filter((item) => {
17873
+ if (graph.getNodeAttributes(item.nodeId).type !== NodeType32.ServiceNode) return false;
17874
+ return item.path.slice(1, -1).every((id) => {
17875
+ const type = graph.getNodeAttributes(id).type;
17876
+ return type === NodeType32.FileNode || type === NodeType32.SymbolNode;
17877
+ });
17878
+ });
17879
+ return {
17880
+ heading: `Services calling ${node} \u2014 ${callers.length}`,
17881
+ facts: callers.length > 0 ? callers.slice(0, MAX_FACTS_PER_SECTION).map((item) => ({
17882
+ text: `${item.nodeId} (distance ${item.distance})`,
17883
+ provenance: item.edgeProvenance,
17884
+ confidence: item.confidence
17885
+ })) : [{ text: `No inbound service callers found for ${node}.` }]
17886
+ };
17887
+ }
17823
17888
  function buildIncidentsSection(node, incidents) {
17824
17889
  const relevant = incidentsForNode(node, incidents);
17825
17890
  if (relevant.length === 0) return null;
@@ -17846,6 +17911,29 @@ function divergenceLine(d) {
17846
17911
  }
17847
17912
  return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
17848
17913
  }
17914
+ function runtimeEdgeState(graph) {
17915
+ let observed = false;
17916
+ let stale = false;
17917
+ graph.forEachEdge((_id, attrs) => {
17918
+ if (attrs.provenance === Provenance27.OBSERVED) observed = true;
17919
+ if (attrs.provenance === Provenance27.STALE) stale = true;
17920
+ });
17921
+ return { observed, stale };
17922
+ }
17923
+ function pendingRuntimeMessage(graph, divergences) {
17924
+ const count = divergences.filter((d) => d.type === "missing-observed").length;
17925
+ if (count === 0) return void 0;
17926
+ const { observed, stale } = runtimeEdgeState(graph);
17927
+ if (observed) return void 0;
17928
+ const prefix = stale ? "No current runtime observations \u2014 earlier runtime evidence is stale" : "No runtime observed yet";
17929
+ return `${prefix} \u2014 ${count} declared dependenc${count === 1 ? "y is" : "ies are"} waiting to be confirmed. Send traces (or connect a provider) and ask again.`;
17930
+ }
17931
+ function noDivergenceMessage(graph) {
17932
+ if (graph.order === 0) return "Graph is empty \u2014 no declared or observed dependencies to compare.";
17933
+ const { observed, stale } = runtimeEdgeState(graph);
17934
+ if (!observed) return stale ? "No current runtime dependency observations to compare \u2014 earlier runtime evidence is stale." : "No runtime dependency observations to compare with declared code.";
17935
+ return "None \u2014 declared code and observed runtime agree across the graph.";
17936
+ }
17849
17937
  function buildDivergenceSection(graph, node, incidents) {
17850
17938
  const result = computeDivergences(graph, { node, ...incidents ? { incidents } : {} });
17851
17939
  if (result.totalAffected === 0) return null;
@@ -17862,19 +17950,28 @@ function buildDivergenceSection(graph, node, incidents) {
17862
17950
  }
17863
17951
  function buildGlobalDivergenceSection(graph, incidents) {
17864
17952
  const result = computeDivergences(graph, incidents ? { incidents } : {});
17953
+ const pending = pendingRuntimeMessage(graph, result.divergences);
17954
+ const visible = pending ? result.divergences.filter((d) => d.type !== "missing-observed") : result.divergences;
17955
+ if (visible.length === 0 && pending) {
17956
+ return {
17957
+ heading: "Divergences (EXTRACTED vs OBSERVED)",
17958
+ facts: [{ text: pending }]
17959
+ };
17960
+ }
17865
17961
  if (result.totalAffected === 0) {
17866
17962
  return {
17867
17963
  heading: "Divergences (EXTRACTED vs OBSERVED)",
17868
- facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
17964
+ facts: [{ text: noDivergenceMessage(graph) }]
17869
17965
  };
17870
17966
  }
17871
- const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
17967
+ const facts = visible.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
17872
17968
  text: divergenceLine(d),
17873
17969
  confidence: d.confidence
17874
17970
  // Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
17875
17971
  }));
17972
+ if (pending && facts.length < MAX_FACTS_PER_SECTION) facts.push({ text: pending });
17876
17973
  return {
17877
- heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
17974
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${visible.length}`,
17878
17975
  facts
17879
17976
  };
17880
17977
  }
@@ -17960,13 +18057,12 @@ function buildOverviewSections(graph, incidents) {
17960
18057
  });
17961
18058
  }
17962
18059
  const div = computeDivergences(graph, incidents ? { incidents } : {});
18060
+ const pending = pendingRuntimeMessage(graph, div.divergences);
18061
+ const visibleCount = pending ? div.divergences.filter((d) => d.type !== "missing-observed").length : div.totalAffected;
18062
+ const divergenceText = visibleCount > 0 ? `${visibleCount} divergence${visibleCount === 1 ? "" : "s"} between declared code and observed runtime \u2014 ask "are there any divergences?" for the list.${pending ? ` ${pending}` : ""}` : pending ?? noDivergenceMessage(graph);
17963
18063
  sections.push({
17964
18064
  heading: "Divergences",
17965
- facts: [
17966
- {
17967
- text: div.totalAffected === 0 ? "None \u2014 declared code and observed runtime agree across the graph." : `${div.totalAffected} divergence${div.totalAffected === 1 ? "" : "s"} between declared code and observed runtime \u2014 ask "are there any divergences?" for the list.`
17968
- }
17969
- ]
18065
+ facts: [{ text: divergenceText }]
17970
18066
  });
17971
18067
  return sections;
17972
18068
  }
@@ -18011,6 +18107,8 @@ function summarizeGlobal(intent, sections) {
18011
18107
  const lead = sections[0];
18012
18108
  switch (intent) {
18013
18109
  case "divergence": {
18110
+ const pending = lead?.facts[0]?.text;
18111
+ if (pending?.startsWith("No runtime") || pending?.startsWith("No current runtime") || pending?.startsWith("Graph is empty")) return pending;
18014
18112
  const none = lead?.facts[0]?.text.startsWith("None") ?? true;
18015
18113
  return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
18016
18114
  }
@@ -18023,11 +18121,16 @@ function summarizeGlobal(intent, sections) {
18023
18121
  return `System overview across the whole graph: ${sections.length} view${sections.length === 1 ? "" : "s"} below \u2014 shape (nodes/edges by provenance), busiest services, incidents, and divergences.`;
18024
18122
  }
18025
18123
  }
18026
- function summarize(question, intent, matched, primary, sections, scope) {
18124
+ function summarize(question, intent, matched, primary, sections, scope, ambiguousType) {
18027
18125
  if (scope === "global") {
18028
18126
  return summarizeGlobal(intent, sections);
18029
18127
  }
18030
18128
  if (!primary) {
18129
+ if (ambiguousType) {
18130
+ const shown = ambiguousType.ids.slice(0, 10);
18131
+ const more = ambiguousType.ids.length - shown.length;
18132
+ return `"${ambiguousType.noun}" matches ${ambiguousType.ids.length} nodes. Name one by ID: ${shown.join(", ")}${more > 0 ? `, and ${more} more` : ""}.`;
18133
+ }
18031
18134
  return `Nothing in "${question}" resolved to a node in the graph. Name a service, file, route, or table \u2014 e.g. \`ask "what does <service> depend on?"\`. For a graph-wide look, ask for an overview, divergences, or incidents.`;
18032
18135
  }
18033
18136
  const lead = sections[0];
@@ -18050,10 +18153,13 @@ function summarize(question, intent, matched, primary, sections, scope) {
18050
18153
  core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
18051
18154
  break;
18052
18155
  case "dependencies":
18053
- core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
18156
+ core = lead?.heading.startsWith("Services calling") ? `${lead.heading}.` : lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
18054
18157
  break;
18055
18158
  case "observed":
18056
- core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
18159
+ {
18160
+ const observed = sections.find((s) => s.heading === "Runtime dependencies (OBSERVED)");
18161
+ core = observed ? `${primary} at runtime: ${observed.facts.length} OBSERVED fact${observed.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
18162
+ }
18057
18163
  break;
18058
18164
  case "incidents":
18059
18165
  core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
@@ -18070,24 +18176,28 @@ async function askGraph(graph, question, opts = {}) {
18070
18176
  const now = opts.now ?? Date.now();
18071
18177
  const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
18072
18178
  const intent = classifyIntent(question);
18073
- const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
18179
+ const resolution = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
18180
+ const matched = resolution.matched;
18074
18181
  const primary = matched[0]?.nodeId;
18075
18182
  const sections = [];
18076
18183
  let scope;
18077
18184
  if (primary) {
18078
18185
  scope = "node";
18186
+ const callersQuestion = intent === "dependencies" && graph.getNodeAttributes(primary).type === NodeType32.DatabaseNode && /\b(?:which|what)\s+services?\s+(?:talks?\s+to|connects?\s+to|uses?|hits?|calls?|reads?\s+from|writes?\s+to)\b/i.test(question);
18187
+ if (callersQuestion) sections.push(buildDatabaseCallersSection(graph, primary));
18079
18188
  for (const kind of SECTION_ORDER[intent]) {
18189
+ if (callersQuestion && kind === "blast") continue;
18080
18190
  const s = buildSection(kind, graph, primary, opts.incidents, now);
18081
18191
  if (s && s.facts.length > 0) sections.push(s);
18082
18192
  }
18083
18193
  } else {
18084
- const global = buildGlobalSections(intent, graph, opts.incidents);
18194
+ const global = resolution.ambiguousType ? null : buildGlobalSections(intent, graph, opts.incidents);
18085
18195
  if (global) {
18086
18196
  scope = "global";
18087
18197
  for (const s of global) if (s.facts.length > 0) sections.push(s);
18088
18198
  }
18089
18199
  }
18090
- const answer = summarize(question, intent, matched, primary, sections, scope);
18200
+ const answer = summarize(question, intent, matched, primary, sections, scope, resolution.ambiguousType);
18091
18201
  const provSet = /* @__PURE__ */ new Set();
18092
18202
  for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
18093
18203
  const confidence = sections[0]?.facts[0]?.confidence;
@@ -19637,9 +19747,9 @@ var RESOURCE_TYPES = [
19637
19747
  function isFirebaseResourceType(value) {
19638
19748
  return RESOURCE_TYPES.includes(value);
19639
19749
  }
19640
- function buildEntriesFilter(sinceIso) {
19750
+ function buildEntriesFilter(sinceIso, resourceTypes = RESOURCE_TYPES) {
19641
19751
  return [
19642
- 'resource.type = ("cloud_function" OR "cloud_run_revision" OR "firebase_domain")',
19752
+ `resource.type = (${resourceTypes.map((t) => `"${t}"`).join(" OR ")})`,
19643
19753
  "httpRequest:*",
19644
19754
  `timestamp >= "${sinceIso}"`
19645
19755
  ].join(" AND ");
@@ -19648,8 +19758,8 @@ var DEFAULT_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
19648
19758
  var ENTRIES_LIST_URL = "https://logging.googleapis.com/v2/entries:list";
19649
19759
  var PAGE_SIZE = 1e3;
19650
19760
  var MAX_PAGES = 20;
19651
- async function fetchHttpRequestLogEntries(creds, sinceIso) {
19652
- const filter = buildEntriesFilter(sinceIso);
19761
+ async function fetchHttpRequestLogEntries(creds, sinceIso, resourceTypes) {
19762
+ const filter = buildEntriesFilter(sinceIso, resourceTypes);
19653
19763
  const out = [];
19654
19764
  let pageToken;
19655
19765
  for (let page = 0; page < MAX_PAGES; page++) {
@@ -19762,7 +19872,37 @@ function mapLogEntriesToSignals(entries) {
19762
19872
  }
19763
19873
 
19764
19874
  // src/connectors/firebase/resolve.ts
19765
- import { NodeType as NodeType35, EdgeType as EdgeType29 } from "@neat.is/types";
19875
+ import { NodeType as NodeType36, EdgeType as EdgeType29 } from "@neat.is/types";
19876
+
19877
+ // src/connectors/infer-service.ts
19878
+ import { NodeType as NodeType35 } from "@neat.is/types";
19879
+ function normalizeName(name) {
19880
+ return name.toLowerCase().replace(/[^a-z0-9]/g, "");
19881
+ }
19882
+ function inferServiceName(graph, resourceName, method, path73) {
19883
+ const wantedMethod = method.toUpperCase();
19884
+ const wantedPath = normalizePathTemplate(path73);
19885
+ const services = [];
19886
+ const owners = /* @__PURE__ */ new Set();
19887
+ graph.forEachNode((_id, attrs) => {
19888
+ const node = attrs;
19889
+ if (node.type === NodeType35.ServiceNode && typeof node.name === "string") {
19890
+ services.push(node.name);
19891
+ return;
19892
+ }
19893
+ if (node.type !== NodeType35.RouteNode) return;
19894
+ const route = attrs;
19895
+ if (!route.pathTemplate || normalizePathTemplate(route.pathTemplate) !== wantedPath) return;
19896
+ const routeMethod = route.method.toUpperCase();
19897
+ if (routeMethod === "ALL" || routeMethod === wantedMethod) owners.add(route.service);
19898
+ });
19899
+ const wanted = normalizeName(resourceName);
19900
+ const byName = services.find((s) => normalizeName(s) === wanted);
19901
+ if (byName) return byName;
19902
+ return owners.size === 1 ? [...owners][0] : null;
19903
+ }
19904
+
19905
+ // src/connectors/firebase/resolve.ts
19766
19906
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
19767
19907
  switch (resourceType) {
19768
19908
  case "cloud_function":
@@ -19777,7 +19917,7 @@ function routeEntriesFor(graph, serviceName) {
19777
19917
  const entries = [];
19778
19918
  graph.forEachNode((_id, attrs) => {
19779
19919
  const node = attrs;
19780
- if (node.type !== NodeType35.RouteNode) return;
19920
+ if (node.type !== NodeType36.RouteNode) return;
19781
19921
  const route = attrs;
19782
19922
  if (route.service !== serviceName) return;
19783
19923
  entries.push({
@@ -19801,9 +19941,9 @@ function createFirebaseResolveTarget(graph, serviceMap) {
19801
19941
  }
19802
19942
  const identity = parseFirebaseTargetName(signal.targetName);
19803
19943
  if (!identity) return null;
19804
- const serviceName = neatServiceNameFor(resourceType, identity.resourceName, serviceMap);
19805
- if (!serviceName) return null;
19806
19944
  const normalizedPath = normalizePathTemplate(identity.path);
19945
+ const serviceName = neatServiceNameFor(resourceType, identity.resourceName, serviceMap) ?? (serviceMap.inferServices ? inferServiceName(graph, identity.resourceName, identity.method, identity.path) : null);
19946
+ if (!serviceName) return null;
19807
19947
  const match = findRoute2(routeEntriesFor(graph, serviceName), identity.method, normalizedPath);
19808
19948
  if (!match) return null;
19809
19949
  return {
@@ -19816,23 +19956,29 @@ function createFirebaseResolveTarget(graph, serviceMap) {
19816
19956
 
19817
19957
  // src/connectors/firebase/index.ts
19818
19958
  var FirebaseConnector = class {
19959
+ constructor(opts = {}) {
19960
+ this.opts = opts;
19961
+ }
19962
+ opts;
19819
19963
  provider = "firebase";
19820
19964
  async poll(ctx) {
19821
19965
  const creds = readFirebaseCredentials(ctx.credentials);
19822
19966
  const sinceIso = ctx.since ?? new Date(Date.now() - DEFAULT_LOOKBACK_MS).toISOString();
19823
- const entries = await fetchHttpRequestLogEntries(creds, sinceIso);
19967
+ const entries = await fetchHttpRequestLogEntries(creds, sinceIso, this.opts.resourceTypes);
19824
19968
  return mapLogEntriesToSignals(entries);
19825
19969
  }
19826
19970
  };
19827
19971
  function createFirebaseConnector(graph, serviceMap) {
19828
19972
  return {
19829
- connector: new FirebaseConnector(),
19973
+ connector: new FirebaseConnector(
19974
+ serviceMap.excludeCloudRun ? { resourceTypes: ["cloud_function", "firebase_domain"] } : {}
19975
+ ),
19830
19976
  resolveTarget: createFirebaseResolveTarget(graph, serviceMap)
19831
19977
  };
19832
19978
  }
19833
19979
 
19834
19980
  // src/connectors/cloudflare/connector.ts
19835
- import { EdgeType as EdgeType30, NodeType as NodeType36, fileId as fileId5, infraId as infraId23 } from "@neat.is/types";
19981
+ import { EdgeType as EdgeType30, NodeType as NodeType37, fileId as fileId5, infraId as infraId23 } from "@neat.is/types";
19836
19982
 
19837
19983
  // src/connectors/cloudflare/client.ts
19838
19984
  import { randomUUID } from "crypto";
@@ -19991,7 +20137,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
19991
20137
  graph.forEachNode((id, attrs) => {
19992
20138
  if (found) return;
19993
20139
  const a = attrs;
19994
- if (a.type === NodeType36.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
20140
+ if (a.type === NodeType37.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
19995
20141
  found = id;
19996
20142
  }
19997
20143
  });
@@ -20003,7 +20149,7 @@ function findMatchingRouteNode(graph, serviceName, method, path73) {
20003
20149
  graph.forEachNode((id, attrs) => {
20004
20150
  if (found) return;
20005
20151
  const a = attrs;
20006
- if (a.type !== NodeType36.RouteNode || a.service !== serviceName) return;
20152
+ if (a.type !== NodeType37.RouteNode || a.service !== serviceName) return;
20007
20153
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
20008
20154
  const routeMethod = (a.method ?? "").toUpperCase();
20009
20155
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -20402,14 +20548,14 @@ function mapLogEntriesToSignals2(entries) {
20402
20548
  }
20403
20549
 
20404
20550
  // src/connectors/cloud-run/resolve.ts
20405
- import { EdgeType as EdgeType32, NodeType as NodeType37, infraId as infraId25 } from "@neat.is/types";
20551
+ import { EdgeType as EdgeType32, NodeType as NodeType38, infraId as infraId25 } from "@neat.is/types";
20406
20552
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
20407
20553
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
20408
20554
  let found = null;
20409
20555
  graph.forEachNode((_id, attrs) => {
20410
20556
  if (found) return;
20411
20557
  const node = attrs;
20412
- if (node.type !== NodeType37.RouteNode) return;
20558
+ if (node.type !== NodeType38.RouteNode) return;
20413
20559
  const route = attrs;
20414
20560
  if (route.service !== serviceName || !route.pathTemplate) return;
20415
20561
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -20425,7 +20571,7 @@ function createCloudRunResolveTarget(graph, config) {
20425
20571
  const identity = parseCloudRunTargetName(signal.targetName);
20426
20572
  if (!identity) return null;
20427
20573
  const { serviceName: gcpServiceName, method, path: path73 } = identity;
20428
- const mappedService = config.serviceMap?.[gcpServiceName];
20574
+ const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method, path73) ?? void 0 : void 0);
20429
20575
  if (mappedService) {
20430
20576
  const routeNodeId = findMatchingRouteNode2(
20431
20577
  graph,
@@ -20613,14 +20759,14 @@ function mapLogEntriesToSignals3(entries) {
20613
20759
  }
20614
20760
 
20615
20761
  // src/connectors/gcp-lb/resolve.ts
20616
- import { EdgeType as EdgeType33, NodeType as NodeType38, infraId as infraId26 } from "@neat.is/types";
20762
+ import { EdgeType as EdgeType33, NodeType as NodeType39, infraId as infraId26 } from "@neat.is/types";
20617
20763
  var GCP_LB_BACKEND_INFRA_KIND = "gcp-lb-backend";
20618
20764
  function findMatchingRouteNode3(graph, serviceName, method, normalizedPath) {
20619
20765
  let found = null;
20620
20766
  graph.forEachNode((_id, attrs) => {
20621
20767
  if (found) return;
20622
20768
  const node = attrs;
20623
- if (node.type !== NodeType38.RouteNode) return;
20769
+ if (node.type !== NodeType39.RouteNode) return;
20624
20770
  const route = attrs;
20625
20771
  if (route.service !== serviceName || !route.pathTemplate) return;
20626
20772
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -20636,7 +20782,7 @@ function createGcpLbResolveTarget(graph, config) {
20636
20782
  const identity = parseGcpLbTargetName(signal.targetName);
20637
20783
  if (!identity) return null;
20638
20784
  const { backendServiceName, method, path: path73 } = identity;
20639
- const mappedService = config.backendServiceMap?.[backendServiceName];
20785
+ const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method, path73) ?? void 0 : void 0);
20640
20786
  if (mappedService) {
20641
20787
  const routeNodeId = findMatchingRouteNode3(
20642
20788
  graph,
@@ -20691,7 +20837,7 @@ function createGcpLbConnector(graph, config = {}) {
20691
20837
  }
20692
20838
 
20693
20839
  // src/connectors/render/index.ts
20694
- import { EdgeType as EdgeType34, NodeType as NodeType39 } from "@neat.is/types";
20840
+ import { EdgeType as EdgeType34, NodeType as NodeType40 } from "@neat.is/types";
20695
20841
 
20696
20842
  // src/connectors/render/types.ts
20697
20843
  function readRenderToken(credentials) {
@@ -20767,7 +20913,7 @@ function buildRenderRouteIndex(graph, serviceName) {
20767
20913
  const out = [];
20768
20914
  graph.forEachNode((_id, attrs) => {
20769
20915
  const node = attrs;
20770
- if (node.type !== NodeType39.RouteNode) return;
20916
+ if (node.type !== NodeType40.RouteNode) return;
20771
20917
  const route = attrs;
20772
20918
  if (route.service !== serviceName) return;
20773
20919
  out.push({
@@ -21251,7 +21397,7 @@ function mapBuildsToSignals(builds, serviceName) {
21251
21397
  }
21252
21398
 
21253
21399
  // src/connectors/eas/resolve.ts
21254
- import { EdgeType as EdgeType36, NodeType as NodeType40, parseFileId as parseFileId3 } from "@neat.is/types";
21400
+ import { EdgeType as EdgeType36, NodeType as NodeType41, parseFileId as parseFileId3 } from "@neat.is/types";
21255
21401
  var NO_ENV2 = "unknown";
21256
21402
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
21257
21403
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -21279,7 +21425,7 @@ function findConfigNode(graph, basenames, serviceName) {
21279
21425
  graph.forEachNode((id, attrs) => {
21280
21426
  if (scoped) return;
21281
21427
  const node = attrs;
21282
- if (node.type !== NodeType40.ConfigNode) return;
21428
+ if (node.type !== NodeType41.ConfigNode) return;
21283
21429
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
21284
21430
  if (anyMatch === null) anyMatch = id;
21285
21431
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -21790,6 +21936,9 @@ var PUSH_PROVIDER_DISPATCH = {
21790
21936
  }
21791
21937
  }
21792
21938
  };
21939
+ function getPushProviderDispatch(provider) {
21940
+ return PUSH_PROVIDER_DISPATCH[provider];
21941
+ }
21793
21942
  function isPushProvider(provider) {
21794
21943
  return provider in PUSH_PROVIDER_DISPATCH;
21795
21944
  }
@@ -23011,6 +23160,7 @@ export {
23011
23160
  bearerAuthHeader,
23012
23161
  junctionFetch,
23013
23162
  PROVIDER_DISPATCH,
23163
+ getPushProviderDispatch,
23014
23164
  isPushProvider,
23015
23165
  getProviderFieldSchema,
23016
23166
  knownProviderNames,
@@ -23020,4 +23170,4 @@ export {
23020
23170
  deprovisionConnector,
23021
23171
  buildApi
23022
23172
  };
23023
- //# sourceMappingURL=chunk-NFJ4W5T6.js.map
23173
+ //# sourceMappingURL=chunk-J3VVEB45.js.map