@neat.is/core 0.3.3 → 0.3.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
@@ -99,8 +99,8 @@ function reshapeGrpcRequest(req) {
99
99
  };
100
100
  }
101
101
  function resolveProtoRoot() {
102
- const here = import_node_path33.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
103
- return import_node_path33.default.resolve(here, "..", "proto");
102
+ const here = import_node_path34.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
103
+ return import_node_path34.default.resolve(here, "..", "proto");
104
104
  }
105
105
  function loadTraceService() {
106
106
  const protoRoot = resolveProtoRoot();
@@ -155,13 +155,13 @@ async function startOtelGrpcReceiver(opts) {
155
155
  })
156
156
  };
157
157
  }
158
- var import_node_url, import_node_path33, grpc, protoLoader;
158
+ var import_node_url, import_node_path34, grpc, protoLoader;
159
159
  var init_otel_grpc = __esm({
160
160
  "src/otel-grpc.ts"() {
161
161
  "use strict";
162
162
  init_cjs_shims();
163
163
  import_node_url = require("url");
164
- import_node_path33 = __toESM(require("path"), 1);
164
+ import_node_path34 = __toESM(require("path"), 1);
165
165
  grpc = __toESM(require("@grpc/grpc-js"), 1);
166
166
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
167
167
  init_otel();
@@ -258,10 +258,10 @@ function parseOtlpRequest(body) {
258
258
  }
259
259
  function loadProtobufDecoder() {
260
260
  if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
261
- const here = import_node_path34.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
262
- const protoRoot = import_node_path34.default.resolve(here, "..", "proto");
261
+ const here = import_node_path35.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
262
+ const protoRoot = import_node_path35.default.resolve(here, "..", "proto");
263
263
  const root = new import_protobufjs.default.Root();
264
- root.resolvePath = (_origin, target) => import_node_path34.default.resolve(protoRoot, target);
264
+ root.resolvePath = (_origin, target) => import_node_path35.default.resolve(protoRoot, target);
265
265
  root.loadSync(
266
266
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
267
267
  { keepCase: true }
@@ -353,12 +353,12 @@ async function buildOtelReceiver(opts) {
353
353
  };
354
354
  return decorated;
355
355
  }
356
- var import_node_path34, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
356
+ var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
357
357
  var init_otel = __esm({
358
358
  "src/otel.ts"() {
359
359
  "use strict";
360
360
  init_cjs_shims();
361
- import_node_path34 = __toESM(require("path"), 1);
361
+ import_node_path35 = __toESM(require("path"), 1);
362
362
  import_node_url2 = require("url");
363
363
  import_fastify2 = __toESM(require("fastify"), 1);
364
364
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -368,7 +368,7 @@ var init_otel = __esm({
368
368
 
369
369
  // src/server.ts
370
370
  init_cjs_shims();
371
- var import_node_path36 = __toESM(require("path"), 1);
371
+ var import_node_path37 = __toESM(require("path"), 1);
372
372
 
373
373
  // src/graph.ts
374
374
  init_cjs_shims();
@@ -716,11 +716,16 @@ init_cjs_shims();
716
716
  var import_types = require("@neat.is/types");
717
717
  var ROOT_CAUSE_MAX_DEPTH = 5;
718
718
  var BLAST_RADIUS_DEFAULT_DEPTH = 10;
719
+ function isFrontierNode(graph, nodeId) {
720
+ if (!graph.hasNode(nodeId)) return false;
721
+ const attrs = graph.getNodeAttributes(nodeId);
722
+ return attrs.type === import_types.NodeType.FrontierNode;
723
+ }
719
724
  function bestEdgeBySource(graph, edgeIds) {
720
725
  const best = /* @__PURE__ */ new Map();
721
726
  for (const id of edgeIds) {
722
727
  const e = graph.getEdgeAttributes(id);
723
- if (e.provenance === import_types.Provenance.FRONTIER) continue;
728
+ if (isFrontierNode(graph, e.source)) continue;
724
729
  const cur = best.get(e.source);
725
730
  if (!cur || import_types.PROV_RANK[e.provenance] > import_types.PROV_RANK[cur.provenance]) {
726
731
  best.set(e.source, e);
@@ -732,7 +737,7 @@ function bestEdgeByTarget(graph, edgeIds) {
732
737
  const best = /* @__PURE__ */ new Map();
733
738
  for (const id of edgeIds) {
734
739
  const e = graph.getEdgeAttributes(id);
735
- if (e.provenance === import_types.Provenance.FRONTIER) continue;
740
+ if (isFrontierNode(graph, e.target)) continue;
736
741
  const cur = best.get(e.target);
737
742
  if (!cur || import_types.PROV_RANK[e.provenance] > import_types.PROV_RANK[cur.provenance]) {
738
743
  best.set(e.target, e);
@@ -744,8 +749,7 @@ var PROVENANCE_CEILING = {
744
749
  OBSERVED: 1,
745
750
  INFERRED: 0.7,
746
751
  EXTRACTED: 0.5,
747
- STALE: 0.3,
748
- FRONTIER: 0.3
752
+ STALE: 0.3
749
753
  };
750
754
  function volumeWeight(spanCount) {
751
755
  if (!spanCount || spanCount <= 0) return 0.5;
@@ -798,19 +802,19 @@ function confidenceFromMix(edges, now = Date.now()) {
798
802
  function longestIncomingWalk(graph, start, maxDepth) {
799
803
  let best = { path: [start], edges: [] };
800
804
  const visited = /* @__PURE__ */ new Set([start]);
801
- function step(node, path37, edges) {
802
- if (path37.length > best.path.length) {
803
- best = { path: [...path37], edges: [...edges] };
805
+ function step(node, path38, edges) {
806
+ if (path38.length > best.path.length) {
807
+ best = { path: [...path38], edges: [...edges] };
804
808
  }
805
- if (path37.length - 1 >= maxDepth) return;
809
+ if (path38.length - 1 >= maxDepth) return;
806
810
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
807
811
  for (const [srcId, edge] of incoming) {
808
812
  if (visited.has(srcId)) continue;
809
813
  visited.add(srcId);
810
- path37.push(srcId);
814
+ path38.push(srcId);
811
815
  edges.push(edge);
812
- step(srcId, path37, edges);
813
- path37.pop();
816
+ step(srcId, path38, edges);
817
+ path38.pop();
814
818
  edges.pop();
815
819
  visited.delete(srcId);
816
820
  }
@@ -1014,9 +1018,6 @@ function bucketEdges(graph) {
1014
1018
  case import_types2.Provenance.INFERRED:
1015
1019
  cur.inferred = e;
1016
1020
  break;
1017
- case import_types2.Provenance.FRONTIER:
1018
- cur.frontier = e;
1019
- break;
1020
1021
  default:
1021
1022
  if (e.provenance === import_types2.Provenance.STALE) cur.stale = e;
1022
1023
  }
@@ -1029,14 +1030,6 @@ function nodeIsFrontier(graph, nodeId) {
1029
1030
  const attrs = graph.getNodeAttributes(nodeId);
1030
1031
  return attrs.type === import_types2.NodeType.FrontierNode;
1031
1032
  }
1032
- function hasAnyObservedFromSource(graph, sourceId) {
1033
- if (!graph.hasNode(sourceId)) return false;
1034
- for (const edgeId of graph.outboundEdges(sourceId)) {
1035
- const e = graph.getEdgeAttributes(edgeId);
1036
- if (e.provenance === import_types2.Provenance.OBSERVED) return true;
1037
- }
1038
- return false;
1039
- }
1040
1033
  function clampConfidence(n) {
1041
1034
  if (!Number.isFinite(n)) return 0;
1042
1035
  return Math.max(0, Math.min(1, n));
@@ -1050,32 +1043,34 @@ function reasonForMissingExtracted(source, target, type) {
1050
1043
  var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
1051
1044
  var RECOMMENDATION_MISSING_EXTRACTED = "Likely dynamic dispatch, reflection, or a coverage gap in tree-sitter extraction. Consider an `aliases` entry on the source service or file an extractor issue.";
1052
1045
  var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
1046
+ function gradedConfidence(edge) {
1047
+ if (typeof edge.confidence === "number") return clampConfidence(edge.confidence);
1048
+ return clampConfidence(confidenceForEdge(edge));
1049
+ }
1053
1050
  function detectMissingDivergences(graph, bucket) {
1054
1051
  const out = [];
1055
1052
  if (bucket.extracted && !bucket.observed) {
1056
1053
  if (!nodeIsFrontier(graph, bucket.target)) {
1057
- const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
1058
1054
  out.push({
1059
1055
  type: "missing-observed",
1060
1056
  source: bucket.source,
1061
1057
  target: bucket.target,
1062
1058
  edgeType: bucket.type,
1063
1059
  extracted: bucket.extracted,
1064
- confidence: sourceHasTraffic ? 1 : 0.5,
1060
+ confidence: gradedConfidence(bucket.extracted),
1065
1061
  reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
1066
1062
  recommendation: RECOMMENDATION_MISSING_OBSERVED
1067
1063
  });
1068
1064
  }
1069
1065
  }
1070
1066
  if (bucket.observed && !bucket.extracted) {
1071
- const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
1072
1067
  out.push({
1073
1068
  type: "missing-extracted",
1074
1069
  source: bucket.source,
1075
1070
  target: bucket.target,
1076
1071
  edgeType: bucket.type,
1077
1072
  observed: bucket.observed,
1078
- confidence: cascaded,
1073
+ confidence: gradedConfidence(bucket.observed),
1079
1074
  reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
1080
1075
  recommendation: RECOMMENDATION_MISSING_EXTRACTED
1081
1076
  });
@@ -1214,8 +1209,17 @@ function computeDivergences(graph, opts = {}) {
1214
1209
  const target = opts.node;
1215
1210
  filtered = filtered.filter((d) => involvesNode(d, target));
1216
1211
  }
1212
+ const TYPE_LEADERSHIP = {
1213
+ "missing-extracted": 0,
1214
+ "missing-observed": 1,
1215
+ "version-mismatch": 2,
1216
+ "host-mismatch": 3,
1217
+ "compat-violation": 4
1218
+ };
1217
1219
  filtered.sort((a, b) => {
1218
1220
  if (b.confidence !== a.confidence) return b.confidence - a.confidence;
1221
+ const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type];
1222
+ if (lead !== 0) return lead;
1219
1223
  if (a.type !== b.type) return a.type.localeCompare(b.type);
1220
1224
  if (a.source !== b.source) return a.source.localeCompare(b.source);
1221
1225
  return a.target.localeCompare(b.target);
@@ -1282,8 +1286,8 @@ var evaluateStructural = ({
1282
1286
  for (const edgeId of graph.outboundEdges(id)) {
1283
1287
  const e = graph.getEdgeAttributes(edgeId);
1284
1288
  if (e.type !== rule.edgeType) continue;
1285
- if (e.provenance === import_types3.Provenance.FRONTIER) continue;
1286
1289
  const target = graph.getNodeAttributes(e.target);
1290
+ if (target.type === import_types3.NodeType.FrontierNode) continue;
1287
1291
  if (target.type === rule.toNodeType) {
1288
1292
  satisfied = true;
1289
1293
  break;
@@ -1402,8 +1406,8 @@ var evaluateCompatibility = ({
1402
1406
  for (const edgeId of graph.outboundEdges(svcId)) {
1403
1407
  const e = graph.getEdgeAttributes(edgeId);
1404
1408
  if (e.type !== import_types3.EdgeType.CONNECTS_TO) continue;
1405
- if (e.provenance === import_types3.Provenance.FRONTIER) continue;
1406
1409
  const dbAttrs = graph.getNodeAttributes(e.target);
1410
+ if (dbAttrs.type === import_types3.NodeType.FrontierNode) continue;
1407
1411
  if (dbAttrs.type !== import_types3.NodeType.DatabaseNode) continue;
1408
1412
  const db = dbAttrs;
1409
1413
  for (const pair of compatPairs()) {
@@ -1729,31 +1733,6 @@ function ensureFrontierNode(graph, host, ts) {
1729
1733
  graph.addNode(id, node);
1730
1734
  return id;
1731
1735
  }
1732
- function upsertFrontierEdge(graph, type, source, target, ts) {
1733
- const id = (0, import_types4.frontierEdgeId)(source, target, type);
1734
- if (graph.hasEdge(id)) {
1735
- const existing = graph.getEdgeAttributes(id);
1736
- const updated = {
1737
- ...existing,
1738
- provenance: import_types4.Provenance.FRONTIER,
1739
- lastObserved: ts,
1740
- callCount: (existing.callCount ?? 0) + 1
1741
- };
1742
- graph.replaceEdgeAttributes(id, updated);
1743
- return;
1744
- }
1745
- const edge = {
1746
- id,
1747
- source,
1748
- target,
1749
- type,
1750
- provenance: import_types4.Provenance.FRONTIER,
1751
- confidence: 1,
1752
- lastObserved: ts,
1753
- callCount: 1
1754
- };
1755
- graph.addEdgeWithKey(id, source, target, edge);
1756
- }
1757
1736
  function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1758
1737
  if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
1759
1738
  const id = makeObservedEdgeId(type, source, target);
@@ -1761,35 +1740,37 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1761
1740
  const existing = graph.getEdgeAttributes(id);
1762
1741
  const newSpanCount = (existing.signal?.spanCount ?? existing.callCount ?? 0) + 1;
1763
1742
  const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0);
1743
+ const newSignal = {
1744
+ spanCount: newSpanCount,
1745
+ errorCount: newErrorCount,
1746
+ lastObservedAgeMs: 0
1747
+ };
1764
1748
  const updated = {
1765
1749
  ...existing,
1766
1750
  provenance: import_types4.Provenance.OBSERVED,
1767
1751
  lastObserved: ts,
1768
1752
  callCount: newSpanCount,
1769
- signal: {
1770
- spanCount: newSpanCount,
1771
- errorCount: newErrorCount,
1772
- lastObservedAgeMs: 0
1773
- },
1774
- confidence: 1
1753
+ signal: newSignal,
1754
+ confidence: (0, import_types4.confidenceForObservedSignal)(newSignal)
1775
1755
  };
1776
1756
  graph.replaceEdgeAttributes(id, updated);
1777
1757
  return { edge: updated, created: false };
1778
1758
  }
1759
+ const signal = {
1760
+ spanCount: 1,
1761
+ errorCount: isError ? 1 : 0,
1762
+ lastObservedAgeMs: 0
1763
+ };
1779
1764
  const edge = {
1780
1765
  id,
1781
1766
  source,
1782
1767
  target,
1783
1768
  type,
1784
1769
  provenance: import_types4.Provenance.OBSERVED,
1785
- confidence: 1,
1770
+ confidence: (0, import_types4.confidenceForObservedSignal)(signal),
1786
1771
  lastObserved: ts,
1787
1772
  callCount: 1,
1788
- signal: {
1789
- spanCount: 1,
1790
- errorCount: isError ? 1 : 0,
1791
- lastObservedAgeMs: 0
1792
- }
1773
+ signal
1793
1774
  };
1794
1775
  graph.addEdgeWithKey(id, source, target, edge);
1795
1776
  return { edge, created: true };
@@ -1837,6 +1818,14 @@ async function appendErrorEvent(ctx, ev) {
1837
1818
  await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(ctx.errorsPath), { recursive: true });
1838
1819
  await import_node_fs3.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
1839
1820
  }
1821
+ function sanitizeAttributes(attrs) {
1822
+ const out = {};
1823
+ for (const [k, v] of Object.entries(attrs)) {
1824
+ if (typeof v === "bigint") out[k] = v.toString();
1825
+ else out[k] = v;
1826
+ }
1827
+ return out;
1828
+ }
1840
1829
  async function handleSpan(ctx, span) {
1841
1830
  const ts = span.startTimeIso ?? nowIso(ctx);
1842
1831
  const nowMs = ctx.now ? ctx.now() : Date.now();
@@ -1876,11 +1865,16 @@ async function handleSpan(ctx, span) {
1876
1865
  affectedNode = targetId;
1877
1866
  resolvedViaAddress = true;
1878
1867
  } else if (!targetId) {
1879
- const frontierId2 = ensureFrontierNode(ctx.graph, host, ts);
1880
- if (ctx.graph.hasNode(sourceId)) {
1881
- upsertFrontierEdge(ctx.graph, import_types4.EdgeType.CALLS, sourceId, frontierId2, ts);
1882
- }
1883
- affectedNode = frontierId2;
1868
+ const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
1869
+ upsertObservedEdge(
1870
+ ctx.graph,
1871
+ import_types4.EdgeType.CALLS,
1872
+ sourceId,
1873
+ frontierNodeId,
1874
+ ts,
1875
+ isError
1876
+ );
1877
+ affectedNode = frontierNodeId;
1884
1878
  resolvedViaAddress = true;
1885
1879
  }
1886
1880
  }
@@ -1902,15 +1896,17 @@ async function handleSpan(ctx, span) {
1902
1896
  if (span.statusCode === 2) {
1903
1897
  stitchTrace(ctx.graph, sourceId, ts);
1904
1898
  if (ctx.writeErrorEventInline !== false) {
1899
+ const attrs = sanitizeAttributes(span.attributes);
1905
1900
  const ev = {
1906
1901
  id: `${span.traceId}:${span.spanId}`,
1907
1902
  timestamp: ts,
1908
1903
  service: span.service,
1909
1904
  traceId: span.traceId,
1910
1905
  spanId: span.spanId,
1911
- errorMessage: span.exception?.message ?? span.errorMessage ?? span.name ?? "unknown error",
1906
+ errorMessage: span.exception?.message ?? span.name ?? "unknown error",
1912
1907
  ...span.exception?.type ? { exceptionType: span.exception.type } : {},
1913
1908
  ...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
1909
+ ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
1914
1910
  affectedNode
1915
1911
  };
1916
1912
  await appendErrorEvent(ctx, ev);
@@ -1966,8 +1962,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId3) {
1966
1962
  }
1967
1963
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
1968
1964
  graph.dropEdge(oldEdgeId);
1969
- const promotedProvenance = edge.provenance === import_types4.Provenance.FRONTIER ? import_types4.Provenance.OBSERVED : edge.provenance;
1970
- const newId = promotedProvenance === import_types4.Provenance.OBSERVED ? (0, import_types4.observedEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types4.Provenance.INFERRED ? (0, import_types4.inferredEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types4.Provenance.EXTRACTED ? (0, import_types4.extractedEdgeId)(newSource, newTarget, edge.type) : (0, import_types4.frontierEdgeId)(newSource, newTarget, edge.type);
1965
+ const newId = edge.provenance === import_types4.Provenance.OBSERVED ? (0, import_types4.observedEdgeId)(newSource, newTarget, edge.type) : edge.provenance === import_types4.Provenance.INFERRED ? (0, import_types4.inferredEdgeId)(newSource, newTarget, edge.type) : (0, import_types4.extractedEdgeId)(newSource, newTarget, edge.type);
1971
1966
  if (graph.hasEdge(newId)) {
1972
1967
  const existing = graph.getEdgeAttributes(newId);
1973
1968
  const merged = {
@@ -1982,8 +1977,7 @@ function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
1982
1977
  ...edge,
1983
1978
  id: newId,
1984
1979
  source: newSource,
1985
- target: newTarget,
1986
- provenance: promotedProvenance
1980
+ target: newTarget
1987
1981
  };
1988
1982
  graph.addEdgeWithKey(newId, newSource, newTarget, rebuilt);
1989
1983
  }
@@ -2393,6 +2387,23 @@ async function writeExtractionErrors(errors, errorsPath) {
2393
2387
  const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
2394
2388
  await import_node_fs7.promises.appendFile(errorsPath, lines, "utf8");
2395
2389
  }
2390
+ var droppedSink = [];
2391
+ function noteExtractedDropped(edge) {
2392
+ droppedSink.push(edge);
2393
+ }
2394
+ function drainDroppedExtracted() {
2395
+ return droppedSink.splice(0, droppedSink.length);
2396
+ }
2397
+ function isRejectedLogEnabled() {
2398
+ const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG;
2399
+ return raw === "1" || raw === "true";
2400
+ }
2401
+ async function writeRejectedExtracted(drops, rejectedPath) {
2402
+ if (drops.length === 0) return;
2403
+ await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(rejectedPath), { recursive: true });
2404
+ const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
2405
+ await import_node_fs7.promises.appendFile(rejectedPath, lines, "utf8");
2406
+ }
2396
2407
 
2397
2408
  // src/extract/services.ts
2398
2409
  var DEFAULT_SCAN_DEPTH = 5;
@@ -3331,6 +3342,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3331
3342
  target: dbNode.id,
3332
3343
  type: import_types8.EdgeType.CONNECTS_TO,
3333
3344
  provenance: import_types8.Provenance.EXTRACTED,
3345
+ confidence: (0, import_types8.confidenceForExtracted)("structural"),
3334
3346
  // ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
3335
3347
  // Ghost-edge cleanup keys retirement on this; the conditional
3336
3348
  // sourceFile spread that used to live here was a v0.1.x leftover.
@@ -3405,6 +3417,7 @@ async function addConfigNodes(graph, services, scanPath) {
3405
3417
  target: node.id,
3406
3418
  type: import_types9.EdgeType.CONFIGURED_BY,
3407
3419
  provenance: import_types9.Provenance.EXTRACTED,
3420
+ confidence: (0, import_types9.confidenceForExtracted)("structural"),
3408
3421
  evidence: { file: relPath.split(import_node_path18.default.sep).join("/") }
3409
3422
  };
3410
3423
  if (!graph.hasEdge(edge.id)) {
@@ -3559,17 +3572,32 @@ async function addHttpCallEdges(graph, services) {
3559
3572
  for (const [targetId, evidenceFile] of seenTargets) {
3560
3573
  const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
3561
3574
  const line = lineOf(fileContent, `//${evidenceFile.host}`);
3575
+ const confidence = (0, import_types10.confidenceForExtracted)("hostname-shape-match");
3576
+ const ev = {
3577
+ file: import_node_path20.default.relative(service.dir, evidenceFile.file),
3578
+ line,
3579
+ snippet: snippet(fileContent, line)
3580
+ };
3581
+ const edgeId = (0, import_types5.extractedEdgeId)(service.node.id, targetId, import_types10.EdgeType.CALLS);
3582
+ if (!(0, import_types10.passesExtractedFloor)(confidence)) {
3583
+ noteExtractedDropped({
3584
+ source: service.node.id,
3585
+ target: targetId,
3586
+ type: import_types10.EdgeType.CALLS,
3587
+ confidence,
3588
+ confidenceKind: "hostname-shape-match",
3589
+ evidence: ev
3590
+ });
3591
+ continue;
3592
+ }
3562
3593
  const edge = {
3563
- id: (0, import_types5.extractedEdgeId)(service.node.id, targetId, import_types10.EdgeType.CALLS),
3594
+ id: edgeId,
3564
3595
  source: service.node.id,
3565
3596
  target: targetId,
3566
3597
  type: import_types10.EdgeType.CALLS,
3567
3598
  provenance: import_types10.Provenance.EXTRACTED,
3568
- evidence: {
3569
- file: import_node_path20.default.relative(service.dir, evidenceFile.file),
3570
- line,
3571
- snippet: snippet(fileContent, line)
3572
- }
3599
+ confidence,
3600
+ evidence: ev
3573
3601
  };
3574
3602
  if (!graph.hasEdge(edge.id)) {
3575
3603
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3608,6 +3636,10 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3608
3636
  name: topic,
3609
3637
  kind: "kafka-topic",
3610
3638
  edgeType,
3639
+ // `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})` —
3640
+ // framework-aware (kafkajs / node-rdkafka shape). Verified-call-site
3641
+ // tier (ADR-066).
3642
+ confidenceKind: "verified-call-site",
3611
3643
  evidence: {
3612
3644
  file: import_node_path21.default.relative(serviceDir, file.path),
3613
3645
  line,
@@ -3640,6 +3672,10 @@ function redisEndpointsFromFile(file, serviceDir) {
3640
3672
  name: host,
3641
3673
  kind: "redis",
3642
3674
  edgeType: "CALLS",
3675
+ // `redis://host` URL literal — the scheme is structural support, but no
3676
+ // call expression is verified to wire it through. URL-with-structural-
3677
+ // support tier (ADR-066).
3678
+ confidenceKind: "url-with-structural-support",
3643
3679
  evidence: {
3644
3680
  file: import_node_path22.default.relative(serviceDir, file.path),
3645
3681
  line,
@@ -3681,6 +3717,10 @@ function awsEndpointsFromFile(file, serviceDir) {
3681
3717
  name,
3682
3718
  kind,
3683
3719
  edgeType: "CALLS",
3720
+ // SDK marker (S3Client, GetCommand, etc.) plus a Bucket/TableName
3721
+ // literal — framework-aware recognizer, verified-call-site tier
3722
+ // (ADR-066).
3723
+ confidenceKind: "verified-call-site",
3684
3724
  evidence: {
3685
3725
  file: import_node_path23.default.relative(serviceDir, file.path),
3686
3726
  line,
@@ -3760,6 +3800,10 @@ function grpcEndpointsFromFile(file, serviceDir) {
3760
3800
  name,
3761
3801
  kind,
3762
3802
  edgeType: "CALLS",
3803
+ // `new <Name>Client(...)` with @aws-sdk/* or @grpc/grpc-js import
3804
+ // context — import-aware classification per #238. Verified-call-site
3805
+ // tier (ADR-066).
3806
+ confidenceKind: "verified-call-site",
3763
3807
  evidence: {
3764
3808
  file: import_node_path24.default.relative(serviceDir, file.path),
3765
3809
  line,
@@ -3820,6 +3864,18 @@ async function addExternalEndpointEdges(graph, services) {
3820
3864
  const edgeId = (0, import_types5.extractedEdgeId)(service.node.id, ep.infraId, edgeType);
3821
3865
  if (seenEdges.has(edgeId)) continue;
3822
3866
  seenEdges.add(edgeId);
3867
+ const confidence = (0, import_types15.confidenceForExtracted)(ep.confidenceKind);
3868
+ if (!(0, import_types15.passesExtractedFloor)(confidence)) {
3869
+ noteExtractedDropped({
3870
+ source: service.node.id,
3871
+ target: ep.infraId,
3872
+ type: edgeType,
3873
+ confidence,
3874
+ confidenceKind: ep.confidenceKind,
3875
+ evidence: ep.evidence
3876
+ });
3877
+ continue;
3878
+ }
3823
3879
  if (!graph.hasEdge(edgeId)) {
3824
3880
  const edge = {
3825
3881
  id: edgeId,
@@ -3827,6 +3883,7 @@ async function addExternalEndpointEdges(graph, services) {
3827
3883
  target: ep.infraId,
3828
3884
  type: edgeType,
3829
3885
  provenance: import_types15.Provenance.EXTRACTED,
3886
+ confidence,
3830
3887
  evidence: ep.evidence
3831
3888
  };
3832
3889
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3946,6 +4003,7 @@ async function addComposeInfra(graph, scanPath, services) {
3946
4003
  target: targetId,
3947
4004
  type: import_types17.EdgeType.DEPENDS_ON,
3948
4005
  provenance: import_types17.Provenance.EXTRACTED,
4006
+ confidence: (0, import_types17.confidenceForExtracted)("structural"),
3949
4007
  evidence: { file: evidenceFile }
3950
4008
  };
3951
4009
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4006,6 +4064,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4006
4064
  target: node.id,
4007
4065
  type: import_types18.EdgeType.RUNS_ON,
4008
4066
  provenance: import_types18.Provenance.EXTRACTED,
4067
+ confidence: (0, import_types18.confidenceForExtracted)("structural"),
4009
4068
  evidence: {
4010
4069
  file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
4011
4070
  }
@@ -4122,6 +4181,9 @@ async function addInfra(graph, scanPath, services) {
4122
4181
  };
4123
4182
  }
4124
4183
 
4184
+ // src/extract/index.ts
4185
+ var import_node_path30 = __toESM(require("path"), 1);
4186
+
4125
4187
  // src/extract/retire.ts
4126
4188
  init_cjs_shims();
4127
4189
  var import_node_fs17 = require("fs");
@@ -4174,13 +4236,26 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4174
4236
  );
4175
4237
  }
4176
4238
  }
4239
+ const droppedEntries = drainDroppedExtracted();
4240
+ if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
4241
+ const rejectedPath = import_node_path30.default.join(import_node_path30.default.dirname(opts.errorsPath), "rejected.ndjson");
4242
+ try {
4243
+ await writeRejectedExtracted(droppedEntries, rejectedPath);
4244
+ } catch (err) {
4245
+ console.warn(
4246
+ `[neat] failed to write rejected extracted edges to ${rejectedPath}: ${err.message}`
4247
+ );
4248
+ }
4249
+ }
4177
4250
  const result = {
4178
4251
  nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
4179
4252
  edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
4180
4253
  frontiersPromoted,
4181
4254
  extractionErrors: errorEntries.length,
4182
4255
  errorEntries,
4183
- ghostsRetired
4256
+ ghostsRetired,
4257
+ extractedDropped: droppedEntries.length,
4258
+ droppedEntries
4184
4259
  };
4185
4260
  emitNeatEvent({
4186
4261
  type: "extraction-complete",
@@ -4274,23 +4349,23 @@ function canonicalJson(value) {
4274
4349
 
4275
4350
  // src/projects.ts
4276
4351
  init_cjs_shims();
4277
- var import_node_path30 = __toESM(require("path"), 1);
4352
+ var import_node_path31 = __toESM(require("path"), 1);
4278
4353
  function pathsForProject(project, baseDir) {
4279
4354
  if (project === DEFAULT_PROJECT) {
4280
4355
  return {
4281
- snapshotPath: import_node_path30.default.join(baseDir, "graph.json"),
4282
- errorsPath: import_node_path30.default.join(baseDir, "errors.ndjson"),
4283
- staleEventsPath: import_node_path30.default.join(baseDir, "stale-events.ndjson"),
4284
- embeddingsCachePath: import_node_path30.default.join(baseDir, "embeddings.json"),
4285
- policyViolationsPath: import_node_path30.default.join(baseDir, "policy-violations.ndjson")
4356
+ snapshotPath: import_node_path31.default.join(baseDir, "graph.json"),
4357
+ errorsPath: import_node_path31.default.join(baseDir, "errors.ndjson"),
4358
+ staleEventsPath: import_node_path31.default.join(baseDir, "stale-events.ndjson"),
4359
+ embeddingsCachePath: import_node_path31.default.join(baseDir, "embeddings.json"),
4360
+ policyViolationsPath: import_node_path31.default.join(baseDir, "policy-violations.ndjson")
4286
4361
  };
4287
4362
  }
4288
4363
  return {
4289
- snapshotPath: import_node_path30.default.join(baseDir, `${project}.json`),
4290
- errorsPath: import_node_path30.default.join(baseDir, `errors.${project}.ndjson`),
4291
- staleEventsPath: import_node_path30.default.join(baseDir, `stale-events.${project}.ndjson`),
4292
- embeddingsCachePath: import_node_path30.default.join(baseDir, `embeddings.${project}.json`),
4293
- policyViolationsPath: import_node_path30.default.join(baseDir, `policy-violations.${project}.ndjson`)
4364
+ snapshotPath: import_node_path31.default.join(baseDir, `${project}.json`),
4365
+ errorsPath: import_node_path31.default.join(baseDir, `errors.${project}.ndjson`),
4366
+ staleEventsPath: import_node_path31.default.join(baseDir, `stale-events.${project}.ndjson`),
4367
+ embeddingsCachePath: import_node_path31.default.join(baseDir, `embeddings.${project}.json`),
4368
+ policyViolationsPath: import_node_path31.default.join(baseDir, `policy-violations.${project}.ndjson`)
4294
4369
  };
4295
4370
  }
4296
4371
  var Projects = class {
@@ -4332,15 +4407,15 @@ function parseExtraProjects(raw) {
4332
4407
  init_cjs_shims();
4333
4408
  var import_node_fs19 = require("fs");
4334
4409
  var import_node_os2 = __toESM(require("os"), 1);
4335
- var import_node_path31 = __toESM(require("path"), 1);
4410
+ var import_node_path32 = __toESM(require("path"), 1);
4336
4411
  var import_types20 = require("@neat.is/types");
4337
4412
  function neatHome() {
4338
4413
  const override = process.env.NEAT_HOME;
4339
- if (override && override.length > 0) return import_node_path31.default.resolve(override);
4340
- return import_node_path31.default.join(import_node_os2.default.homedir(), ".neat");
4414
+ if (override && override.length > 0) return import_node_path32.default.resolve(override);
4415
+ return import_node_path32.default.join(import_node_os2.default.homedir(), ".neat");
4341
4416
  }
4342
4417
  function registryPath() {
4343
- return import_node_path31.default.join(neatHome(), "projects.json");
4418
+ return import_node_path32.default.join(neatHome(), "projects.json");
4344
4419
  }
4345
4420
  async function readRegistry() {
4346
4421
  const file = registryPath();
@@ -4850,8 +4925,9 @@ async function buildApi(opts) {
4850
4925
  // src/persist.ts
4851
4926
  init_cjs_shims();
4852
4927
  var import_node_fs20 = require("fs");
4853
- var import_node_path32 = __toESM(require("path"), 1);
4854
- var SCHEMA_VERSION = 2;
4928
+ var import_node_path33 = __toESM(require("path"), 1);
4929
+ var import_types22 = require("@neat.is/types");
4930
+ var SCHEMA_VERSION = 3;
4855
4931
  function migrateV1ToV2(payload) {
4856
4932
  const nodes = payload.graph.nodes;
4857
4933
  if (Array.isArray(nodes)) {
@@ -4863,8 +4939,27 @@ function migrateV1ToV2(payload) {
4863
4939
  }
4864
4940
  return { ...payload, schemaVersion: 2 };
4865
4941
  }
4942
+ function migrateV2ToV3(payload) {
4943
+ const edges = payload.graph.edges;
4944
+ if (Array.isArray(edges)) {
4945
+ for (const edge of edges) {
4946
+ const attrs = edge.attributes;
4947
+ if (!attrs || attrs.provenance !== "FRONTIER") continue;
4948
+ attrs.provenance = import_types22.Provenance.OBSERVED;
4949
+ const type = typeof attrs.type === "string" ? attrs.type : void 0;
4950
+ const source = typeof attrs.source === "string" ? attrs.source : void 0;
4951
+ const target = typeof attrs.target === "string" ? attrs.target : void 0;
4952
+ if (type && source && target) {
4953
+ const newId = (0, import_types22.observedEdgeId)(source, target, type);
4954
+ attrs.id = newId;
4955
+ if (edge.key) edge.key = newId;
4956
+ }
4957
+ }
4958
+ }
4959
+ return { ...payload, schemaVersion: 3 };
4960
+ }
4866
4961
  async function ensureDir(filePath) {
4867
- await import_node_fs20.promises.mkdir(import_node_path32.default.dirname(filePath), { recursive: true });
4962
+ await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
4868
4963
  }
4869
4964
  async function saveGraphToDisk(graph, outPath) {
4870
4965
  await ensureDir(outPath);
@@ -4889,6 +4984,9 @@ async function loadGraphFromDisk(graph, outPath) {
4889
4984
  if (payload.schemaVersion === 1) {
4890
4985
  payload = migrateV1ToV2(payload);
4891
4986
  }
4987
+ if (payload.schemaVersion === 2) {
4988
+ payload = migrateV2ToV3(payload);
4989
+ }
4892
4990
  if (payload.schemaVersion !== SCHEMA_VERSION) {
4893
4991
  throw new Error(
4894
4992
  `persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`
@@ -4938,7 +5036,7 @@ init_otel_grpc();
4938
5036
  // src/search.ts
4939
5037
  init_cjs_shims();
4940
5038
  var import_node_fs21 = require("fs");
4941
- var import_node_path35 = __toESM(require("path"), 1);
5039
+ var import_node_path36 = __toESM(require("path"), 1);
4942
5040
  var import_node_crypto = require("crypto");
4943
5041
  var DEFAULT_LIMIT = 10;
4944
5042
  var NOMIC_DIM = 768;
@@ -5077,7 +5175,7 @@ async function readCache(cachePath) {
5077
5175
  }
5078
5176
  }
5079
5177
  async function writeCache(cachePath, cache) {
5080
- await import_node_fs21.promises.mkdir(import_node_path35.default.dirname(cachePath), { recursive: true });
5178
+ await import_node_fs21.promises.mkdir(import_node_path36.default.dirname(cachePath), { recursive: true });
5081
5179
  await import_node_fs21.promises.writeFile(cachePath, JSON.stringify(cache));
5082
5180
  }
5083
5181
  var VectorIndex = class {
@@ -5257,14 +5355,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
5257
5355
  async function main() {
5258
5356
  const baseDirEnv = process.env.NEAT_OUT_DIR;
5259
5357
  const legacyOutPath = process.env.NEAT_OUT_PATH;
5260
- const baseDir = baseDirEnv ? import_node_path36.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path36.default.resolve(import_node_path36.default.dirname(legacyOutPath)) : import_node_path36.default.resolve("./neat-out");
5261
- const defaultScanPath = import_node_path36.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
5358
+ const baseDir = baseDirEnv ? import_node_path37.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path37.default.resolve(import_node_path37.default.dirname(legacyOutPath)) : import_node_path37.default.resolve("./neat-out");
5359
+ const defaultScanPath = import_node_path37.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
5262
5360
  const registry = new Projects();
5263
5361
  await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
5264
5362
  for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
5265
5363
  const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
5266
5364
  const projectScan = process.env[envKey];
5267
- await bootProject(registry, name, projectScan ? import_node_path36.default.resolve(projectScan) : void 0, baseDir);
5365
+ await bootProject(registry, name, projectScan ? import_node_path37.default.resolve(projectScan) : void 0, baseDir);
5268
5366
  }
5269
5367
  const host = process.env.HOST ?? "0.0.0.0";
5270
5368
  const port = Number(process.env.PORT ?? 8080);