@neat.is/core 0.4.31-dev.20260718 → 0.5.1-dev.20260719

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.
@@ -483,7 +483,7 @@ function databaseRootCauseShape(graph, origin, walk3) {
483
483
  for (const id of walk3.path) {
484
484
  const owner = resolveOwningService(graph, id);
485
485
  if (!owner) continue;
486
- const { id: serviceId4, svc } = owner;
486
+ const { id: serviceId5, svc } = owner;
487
487
  const deps = svc.dependencies ?? {};
488
488
  for (const pair of candidatePairs) {
489
489
  const declared = deps[pair.driver];
@@ -496,7 +496,7 @@ function databaseRootCauseShape(graph, origin, walk3) {
496
496
  );
497
497
  if (!result.compatible) {
498
498
  return {
499
- rootCauseNode: serviceId4,
499
+ rootCauseNode: serviceId5,
500
500
  rootCauseReason: result.reason ?? "incompatible driver",
501
501
  ...result.minDriverVersion ? {
502
502
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -511,7 +511,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
511
511
  for (const id of walk3.path) {
512
512
  const owner = resolveOwningService(graph, id);
513
513
  if (!owner) continue;
514
- const { id: serviceId4, svc } = owner;
514
+ const { id: serviceId5, svc } = owner;
515
515
  const deps = svc.dependencies ?? {};
516
516
  const serviceNodeEngine = svc.nodeEngine;
517
517
  for (const constraint of nodeEngineConstraints()) {
@@ -520,7 +520,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
520
520
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
521
521
  if (!result.compatible && result.reason) {
522
522
  return {
523
- rootCauseNode: serviceId4,
523
+ rootCauseNode: serviceId5,
524
524
  rootCauseReason: result.reason,
525
525
  ...result.requiredNodeVersion ? {
526
526
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -535,7 +535,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
535
535
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
536
536
  if (!result.compatible && result.reason) {
537
537
  return {
538
- rootCauseNode: serviceId4,
538
+ rootCauseNode: serviceId5,
539
539
  rootCauseReason: result.reason,
540
540
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
541
541
  };
@@ -626,9 +626,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
626
626
  function isFailingCallEdge(e) {
627
627
  return e.type === EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
628
628
  }
629
- function callSourcesForService(graph, serviceId4) {
630
- const ids = [serviceId4];
631
- for (const edgeId of graph.outboundEdges(serviceId4)) {
629
+ function callSourcesForService(graph, serviceId5) {
630
+ const ids = [serviceId5];
631
+ for (const edgeId of graph.outboundEdges(serviceId5)) {
632
632
  const e = graph.getEdgeAttributes(edgeId);
633
633
  if (e.type !== EdgeType.CONTAINS) continue;
634
634
  const tgt = graph.getNodeAttributes(e.target);
@@ -645,9 +645,9 @@ function failingCallDominates(e, id, curEdge, curId) {
645
645
  }
646
646
  return id < curId;
647
647
  }
648
- function dominantFailingCall(graph, serviceId4, visited) {
648
+ function dominantFailingCall(graph, serviceId5, visited) {
649
649
  let best = null;
650
- for (const src of callSourcesForService(graph, serviceId4)) {
650
+ for (const src of callSourcesForService(graph, serviceId5)) {
651
651
  for (const edgeId of graph.outboundEdges(src)) {
652
652
  const e = graph.getEdgeAttributes(edgeId);
653
653
  if (!isFailingCallEdge(e)) continue;
@@ -1923,6 +1923,27 @@ function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
1923
1923
  graph.addNode(id, node);
1924
1924
  return id;
1925
1925
  }
1926
+ function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
1927
+ if (!graph.hasNode(serviceNodeId)) return null;
1928
+ const sources = [serviceNodeId];
1929
+ for (const edgeId of graph.outboundEdges(serviceNodeId)) {
1930
+ const e = graph.getEdgeAttributes(edgeId);
1931
+ if (e.type === EdgeType3.CONTAINS) sources.push(e.target);
1932
+ }
1933
+ const matches = /* @__PURE__ */ new Set();
1934
+ for (const src of sources) {
1935
+ if (!graph.hasNode(src)) continue;
1936
+ for (const edgeId of graph.outboundEdges(src)) {
1937
+ const edge = graph.getEdgeAttributes(edgeId);
1938
+ if (edge.type !== EdgeType3.CONNECTS_TO || edge.provenance !== Provenance2.EXTRACTED) continue;
1939
+ if (!graph.hasNode(edge.target)) continue;
1940
+ const target = graph.getNodeAttributes(edge.target);
1941
+ if (target.type !== NodeType3.DatabaseNode || target.engine !== engine) continue;
1942
+ matches.add(edge.target);
1943
+ }
1944
+ }
1945
+ return matches.size === 1 ? [...matches][0] : null;
1946
+ }
1926
1947
  function ensureFrontierNode(graph, host, ts) {
1927
1948
  const id = frontierIdFor(host);
1928
1949
  if (graph.hasNode(id)) {
@@ -1943,6 +1964,7 @@ function ensureFrontierNode(graph, host, ts) {
1943
1964
  }
1944
1965
  function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
1945
1966
  if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
1967
+ const grain = source.startsWith("file:") ? "file" : "service";
1946
1968
  const id = makeObservedEdgeId(type, source, target);
1947
1969
  if (graph.hasEdge(id)) {
1948
1970
  const existing = graph.getEdgeAttributes(id);
@@ -1959,7 +1981,9 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
1959
1981
  lastObserved: ts,
1960
1982
  callCount: newSpanCount,
1961
1983
  signal: newSignal,
1962
- confidence: confidenceForObservedSignal(newSignal)
1984
+ confidence: confidenceForObservedSignal(newSignal),
1985
+ grain
1986
+ // backfills legacy edges that predate ADR-142
1963
1987
  };
1964
1988
  graph.replaceEdgeAttributes(id, updated);
1965
1989
  return { edge: updated, created: false };
@@ -1979,6 +2003,7 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
1979
2003
  lastObserved: ts,
1980
2004
  callCount: 1,
1981
2005
  signal,
2006
+ grain,
1982
2007
  // Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
1983
2008
  // Only set when code.filepath was present on the span — never fabricated.
1984
2009
  ...evidence ? { evidence } : {}
@@ -2181,13 +2206,18 @@ async function handleSpan(ctx, span) {
2181
2206
  ensureDatabaseNode(ctx.graph, host, span.dbSystem);
2182
2207
  targetId = databaseId(host);
2183
2208
  } else {
2184
- const localName = span.dbName ?? span.dbSystem;
2185
- targetId = ensureLocalDatabaseNode(
2186
- ctx.graph,
2187
- span.service,
2188
- localName,
2189
- span.dbSystem
2190
- );
2209
+ const declared = findDeclaredDatabaseForService(ctx.graph, sourceId, span.dbSystem);
2210
+ if (declared) {
2211
+ targetId = declared;
2212
+ } else {
2213
+ const localName = span.dbName ?? span.dbSystem;
2214
+ targetId = ensureLocalDatabaseNode(
2215
+ ctx.graph,
2216
+ span.service,
2217
+ localName,
2218
+ span.dbSystem
2219
+ );
2220
+ }
2191
2221
  }
2192
2222
  const result = upsertObservedEdge(
2193
2223
  ctx.graph,
@@ -2371,29 +2401,29 @@ function promoteFrontierNodes(graph, opts = {}) {
2371
2401
  toPromote.push({ frontierId: id, serviceId: target });
2372
2402
  });
2373
2403
  let promoted = 0;
2374
- for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
2404
+ for (const { frontierId: frontierId2, serviceId: serviceId5 } of toPromote) {
2375
2405
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
2376
2406
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
2377
2407
  if (!gate.allowed) {
2378
2408
  continue;
2379
2409
  }
2380
2410
  }
2381
- rewireFrontierEdges(graph, frontierId2, serviceId4);
2411
+ rewireFrontierEdges(graph, frontierId2, serviceId5);
2382
2412
  graph.dropNode(frontierId2);
2383
2413
  promoted++;
2384
2414
  }
2385
2415
  return promoted;
2386
2416
  }
2387
- function rewireFrontierEdges(graph, frontierId2, serviceId4) {
2417
+ function rewireFrontierEdges(graph, frontierId2, serviceId5) {
2388
2418
  const inbound = [...graph.inboundEdges(frontierId2)];
2389
2419
  const outbound = [...graph.outboundEdges(frontierId2)];
2390
2420
  for (const edgeId of inbound) {
2391
2421
  const edge = graph.getEdgeAttributes(edgeId);
2392
- rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
2422
+ rebuildEdge(graph, edge, edge.source, serviceId5, edgeId);
2393
2423
  }
2394
2424
  for (const edgeId of outbound) {
2395
2425
  const edge = graph.getEdgeAttributes(edgeId);
2396
- rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
2426
+ rebuildEdge(graph, edge, serviceId5, edge.target, edgeId);
2397
2427
  }
2398
2428
  }
2399
2429
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -3195,9 +3225,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
3195
3225
  "StatefulSet",
3196
3226
  "DaemonSet"
3197
3227
  ]);
3198
- function addAliases(graph, serviceId4, candidates) {
3199
- if (!graph.hasNode(serviceId4)) return;
3200
- const node = graph.getNodeAttributes(serviceId4);
3228
+ function addAliases(graph, serviceId5, candidates) {
3229
+ if (!graph.hasNode(serviceId5)) return;
3230
+ const node = graph.getNodeAttributes(serviceId5);
3201
3231
  if (node.type !== NodeType5.ServiceNode) return;
3202
3232
  const set = new Set(node.aliases ?? []);
3203
3233
  for (const c of candidates) {
@@ -3207,7 +3237,7 @@ function addAliases(graph, serviceId4, candidates) {
3207
3237
  }
3208
3238
  if (set.size === 0) return;
3209
3239
  const updated = { ...node, aliases: [...set].sort() };
3210
- graph.replaceNodeAttributes(serviceId4, updated);
3240
+ graph.replaceNodeAttributes(serviceId5, updated);
3211
3241
  }
3212
3242
  function indexServicesByName(services) {
3213
3243
  const map = /* @__PURE__ */ new Map();
@@ -3240,12 +3270,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
3240
3270
  }
3241
3271
  if (!compose?.services) return;
3242
3272
  for (const [composeName, svc] of Object.entries(compose.services)) {
3243
- const serviceId4 = serviceIndex.get(composeName);
3244
- if (!serviceId4) continue;
3273
+ const serviceId5 = serviceIndex.get(composeName);
3274
+ if (!serviceId5) continue;
3245
3275
  const aliases = /* @__PURE__ */ new Set([composeName]);
3246
3276
  if (svc.container_name) aliases.add(svc.container_name);
3247
3277
  if (svc.hostname) aliases.add(svc.hostname);
3248
- addAliases(graph, serviceId4, aliases);
3278
+ addAliases(graph, serviceId5, aliases);
3249
3279
  }
3250
3280
  }
3251
3281
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -3906,6 +3936,29 @@ async function readIfExists(filePath) {
3906
3936
  return null;
3907
3937
  }
3908
3938
  }
3939
+ async function resolveEnvVar(serviceDir, name) {
3940
+ const entries = await fs12.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3941
+ const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
3942
+ const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
3943
+ envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
3944
+ for (const fileName of envNames) {
3945
+ const content = await readIfExists(path14.join(serviceDir, fileName));
3946
+ if (!content) continue;
3947
+ for (const line of content.split("\n")) {
3948
+ const trimmed = line.trim();
3949
+ if (!trimmed || trimmed.startsWith("#")) continue;
3950
+ const eq = trimmed.indexOf("=");
3951
+ if (eq < 0) continue;
3952
+ if (trimmed.slice(0, eq).trim() !== name) continue;
3953
+ let value = trimmed.slice(eq + 1).trim();
3954
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
3955
+ value = value.slice(1, -1);
3956
+ }
3957
+ return value || null;
3958
+ }
3959
+ }
3960
+ return null;
3961
+ }
3909
3962
  async function findFirst(serviceDir, candidates) {
3910
3963
  for (const rel of candidates) {
3911
3964
  const abs = path14.join(serviceDir, rel);
@@ -4002,6 +4055,14 @@ async function parse3(serviceDir) {
4002
4055
  const config = parseConnectionString(urlMatch[1]);
4003
4056
  if (config) return [{ ...config, sourceFile: schemaPath }];
4004
4057
  }
4058
+ const envMatch = body.match(/url\s*=\s*env\(\s*"([^"]+)"\s*\)/);
4059
+ if (envMatch) {
4060
+ const resolved = await resolveEnvVar(serviceDir, envMatch[1]);
4061
+ if (resolved) {
4062
+ const config = parseConnectionString(resolved);
4063
+ if (config) return [{ ...config, sourceFile: schemaPath }];
4064
+ }
4065
+ }
4005
4066
  return [
4006
4067
  {
4007
4068
  host: `${engine}-prisma`,
@@ -4044,6 +4105,15 @@ async function parse4(serviceDir) {
4044
4105
  const config = parseConnectionString(urlMatch[1]);
4045
4106
  if (config) return [{ ...config, sourceFile: filePath }];
4046
4107
  }
4108
+ const urlEnvMatch = content.match(
4109
+ /(?:url|connectionString)\s*:\s*process\.env(?:\.([A-Za-z_$][\w$]*)|\[\s*['"]([^'"]+)['"]\s*\])/
4110
+ );
4111
+ if (urlEnvMatch) {
4112
+ const varName = urlEnvMatch[1] ?? urlEnvMatch[2];
4113
+ const resolved = varName ? await resolveEnvVar(serviceDir, varName) : null;
4114
+ const config = resolved ? parseConnectionString(resolved) : null;
4115
+ if (config) return [{ ...config, sourceFile: filePath }];
4116
+ }
4047
4117
  const hostMatch = content.match(/host\s*:\s*['"`]([^'"`]+)['"`]/);
4048
4118
  if (hostMatch) {
4049
4119
  const portMatch = content.match(/port\s*:\s*(\d+)/);
@@ -4096,6 +4166,15 @@ async function parse5(serviceDir) {
4096
4166
  const config = parseConnectionString(urlMatch[1]);
4097
4167
  if (config) return [{ ...config, sourceFile: filePath }];
4098
4168
  }
4169
+ const urlEnvMatch = content.match(
4170
+ /connection\s*:\s*process\.env(?:\.([A-Za-z_$][\w$]*)|\[\s*['"]([^'"]+)['"]\s*\])/
4171
+ );
4172
+ if (urlEnvMatch) {
4173
+ const varName = urlEnvMatch[1] ?? urlEnvMatch[2];
4174
+ const resolved = varName ? await resolveEnvVar(serviceDir, varName) : null;
4175
+ const config = resolved ? parseConnectionString(resolved) : null;
4176
+ if (config) return [{ ...config, sourceFile: filePath }];
4177
+ }
4099
4178
  const host = content.match(/host\s*:\s*['"`]([^'"`]+)['"`]/)?.[1];
4100
4179
  if (host) {
4101
4180
  const port = content.match(/port\s*:\s*(\d+)/)?.[1];
@@ -5667,6 +5746,16 @@ function constructorMatchesImport(name, ctx) {
5667
5746
  if (name === "createClient") return ctx.hasSupabaseJs;
5668
5747
  return ctx.hasSupabaseSsr;
5669
5748
  }
5749
+ var SUPABASE_CLIENT_ASSIGN_RE = /(?:const|let|var)\s+(\w+)\s*=\s*(?:await\s+)?(createClient|createServerClient|createBrowserClient)\s*\(/g;
5750
+ function supabaseClientVars(content, ctx) {
5751
+ const vars = /* @__PURE__ */ new Set();
5752
+ SUPABASE_CLIENT_ASSIGN_RE.lastIndex = 0;
5753
+ let m;
5754
+ while ((m = SUPABASE_CLIENT_ASSIGN_RE.exec(content)) !== null) {
5755
+ if (constructorMatchesImport(m[2], ctx)) vars.add(m[1]);
5756
+ }
5757
+ return vars;
5758
+ }
5670
5759
  function supabaseEndpointsFromFile(file, serviceDir) {
5671
5760
  const ctx = readImports2(file.content);
5672
5761
  if (!ctx.hasSupabaseJs && !ctx.hasSupabaseSsr) return [];
@@ -5699,6 +5788,34 @@ function supabaseEndpointsFromFile(file, serviceDir) {
5699
5788
  }
5700
5789
  });
5701
5790
  }
5791
+ const clientVars = supabaseClientVars(file.content, ctx);
5792
+ for (const clientVar of clientVars) {
5793
+ const accessRe = new RegExp(
5794
+ `\\b${clientVar}\\s*\\.\\s*(from|rpc)\\s*\\(\\s*['"\`]([\\w.-]+)['"\`]`,
5795
+ "g"
5796
+ );
5797
+ let am;
5798
+ while ((am = accessRe.exec(file.content)) !== null) {
5799
+ const kind = am[1] === "rpc" ? "supabase-rpc" : "supabase-table";
5800
+ const resource = am[2];
5801
+ const key = `${kind}/${resource}`;
5802
+ if (seen.has(key)) continue;
5803
+ seen.add(key);
5804
+ const line = lineOf(file.content, am[0]);
5805
+ out.push({
5806
+ infraId: infraId6(kind, resource),
5807
+ name: resource,
5808
+ kind,
5809
+ edgeType: "CALLS",
5810
+ confidenceKind: "verified-call-site",
5811
+ evidence: {
5812
+ file: path30.relative(serviceDir, file.path),
5813
+ line,
5814
+ snippet: snippet(file.content, line)
5815
+ }
5816
+ });
5817
+ }
5818
+ }
5702
5819
  return out;
5703
5820
  }
5704
5821
 
@@ -6768,19 +6885,30 @@ import {
6768
6885
  EdgeType as EdgeType20,
6769
6886
  NodeType as NodeType15,
6770
6887
  parseEdgeId,
6771
- Provenance as Provenance18
6888
+ parseFileId,
6889
+ Provenance as Provenance18,
6890
+ serviceId as serviceId4
6772
6891
  } from "@neat.is/types";
6773
6892
  function bucketKey(source, target, type) {
6774
6893
  return `${type}|${source}|${target}`;
6775
6894
  }
6895
+ function bucketSourceFor(graph, edge) {
6896
+ if (edge.type !== EdgeType20.CONNECTS_TO) return edge.source;
6897
+ const parsed = parseFileId(edge.source);
6898
+ if (!parsed || !graph.hasNode(edge.target)) return edge.source;
6899
+ const target = graph.getNodeAttributes(edge.target);
6900
+ if (target.type !== NodeType15.DatabaseNode) return edge.source;
6901
+ return serviceId4(parsed.service);
6902
+ }
6776
6903
  function bucketEdges(graph) {
6777
6904
  const buckets = /* @__PURE__ */ new Map();
6778
6905
  graph.forEachEdge((id, attrs) => {
6779
6906
  const e = attrs;
6780
6907
  const parsed = parseEdgeId(id);
6781
6908
  const provenance = parsed?.provenance ?? e.provenance;
6782
- const key = bucketKey(e.source, e.target, e.type);
6783
- const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
6909
+ const source = bucketSourceFor(graph, e);
6910
+ const key = bucketKey(source, e.target, e.type);
6911
+ const cur = buckets.get(key) ?? { source, target: e.target, type: e.type };
6784
6912
  switch (provenance) {
6785
6913
  case Provenance18.EXTRACTED:
6786
6914
  cur.extracted = e;
@@ -9233,4 +9361,4 @@ export {
9233
9361
  recordConnectorPoll,
9234
9362
  buildApi
9235
9363
  };
9236
- //# sourceMappingURL=chunk-4M2YMGUM.js.map
9364
+ //# sourceMappingURL=chunk-BI3XKGVG.js.map