@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/neatd.cjs CHANGED
@@ -100,8 +100,8 @@ function reshapeGrpcRequest(req2) {
100
100
  };
101
101
  }
102
102
  function resolveProtoRoot() {
103
- const here = import_node_path33.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
104
- return import_node_path33.default.resolve(here, "..", "proto");
103
+ const here = import_node_path34.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
104
+ return import_node_path34.default.resolve(here, "..", "proto");
105
105
  }
106
106
  function loadTraceService() {
107
107
  const protoRoot = resolveProtoRoot();
@@ -156,13 +156,13 @@ async function startOtelGrpcReceiver(opts) {
156
156
  })
157
157
  };
158
158
  }
159
- var import_node_url, import_node_path33, grpc, protoLoader;
159
+ var import_node_url, import_node_path34, grpc, protoLoader;
160
160
  var init_otel_grpc = __esm({
161
161
  "src/otel-grpc.ts"() {
162
162
  "use strict";
163
163
  init_cjs_shims();
164
164
  import_node_url = require("url");
165
- import_node_path33 = __toESM(require("path"), 1);
165
+ import_node_path34 = __toESM(require("path"), 1);
166
166
  grpc = __toESM(require("@grpc/grpc-js"), 1);
167
167
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
168
168
  init_otel();
@@ -259,10 +259,10 @@ function parseOtlpRequest(body) {
259
259
  }
260
260
  function loadProtobufDecoder() {
261
261
  if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
262
- const here = import_node_path34.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
263
- const protoRoot = import_node_path34.default.resolve(here, "..", "proto");
262
+ const here = import_node_path35.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
263
+ const protoRoot = import_node_path35.default.resolve(here, "..", "proto");
264
264
  const root = new import_protobufjs.default.Root();
265
- root.resolvePath = (_origin, target) => import_node_path34.default.resolve(protoRoot, target);
265
+ root.resolvePath = (_origin, target) => import_node_path35.default.resolve(protoRoot, target);
266
266
  root.loadSync(
267
267
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
268
268
  { keepCase: true }
@@ -354,12 +354,12 @@ async function buildOtelReceiver(opts) {
354
354
  };
355
355
  return decorated;
356
356
  }
357
- var import_node_path34, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
357
+ var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
358
358
  var init_otel = __esm({
359
359
  "src/otel.ts"() {
360
360
  "use strict";
361
361
  init_cjs_shims();
362
- import_node_path34 = __toESM(require("path"), 1);
362
+ import_node_path35 = __toESM(require("path"), 1);
363
363
  import_node_url2 = require("url");
364
364
  import_fastify2 = __toESM(require("fastify"), 1);
365
365
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -370,12 +370,12 @@ var init_otel = __esm({
370
370
  // src/neatd.ts
371
371
  init_cjs_shims();
372
372
  var import_node_fs22 = require("fs");
373
- var import_node_path37 = __toESM(require("path"), 1);
373
+ var import_node_path38 = __toESM(require("path"), 1);
374
374
 
375
375
  // src/daemon.ts
376
376
  init_cjs_shims();
377
377
  var import_node_fs21 = require("fs");
378
- var import_node_path35 = __toESM(require("path"), 1);
378
+ var import_node_path36 = __toESM(require("path"), 1);
379
379
 
380
380
  // src/graph.ts
381
381
  init_cjs_shims();
@@ -749,11 +749,16 @@ init_cjs_shims();
749
749
  var import_types = require("@neat.is/types");
750
750
  var ROOT_CAUSE_MAX_DEPTH = 5;
751
751
  var BLAST_RADIUS_DEFAULT_DEPTH = 10;
752
+ function isFrontierNode(graph, nodeId) {
753
+ if (!graph.hasNode(nodeId)) return false;
754
+ const attrs = graph.getNodeAttributes(nodeId);
755
+ return attrs.type === import_types.NodeType.FrontierNode;
756
+ }
752
757
  function bestEdgeBySource(graph, edgeIds) {
753
758
  const best = /* @__PURE__ */ new Map();
754
759
  for (const id of edgeIds) {
755
760
  const e = graph.getEdgeAttributes(id);
756
- if (e.provenance === import_types.Provenance.FRONTIER) continue;
761
+ if (isFrontierNode(graph, e.source)) continue;
757
762
  const cur = best.get(e.source);
758
763
  if (!cur || import_types.PROV_RANK[e.provenance] > import_types.PROV_RANK[cur.provenance]) {
759
764
  best.set(e.source, e);
@@ -765,7 +770,7 @@ function bestEdgeByTarget(graph, edgeIds) {
765
770
  const best = /* @__PURE__ */ new Map();
766
771
  for (const id of edgeIds) {
767
772
  const e = graph.getEdgeAttributes(id);
768
- if (e.provenance === import_types.Provenance.FRONTIER) continue;
773
+ if (isFrontierNode(graph, e.target)) continue;
769
774
  const cur = best.get(e.target);
770
775
  if (!cur || import_types.PROV_RANK[e.provenance] > import_types.PROV_RANK[cur.provenance]) {
771
776
  best.set(e.target, e);
@@ -777,8 +782,7 @@ var PROVENANCE_CEILING = {
777
782
  OBSERVED: 1,
778
783
  INFERRED: 0.7,
779
784
  EXTRACTED: 0.5,
780
- STALE: 0.3,
781
- FRONTIER: 0.3
785
+ STALE: 0.3
782
786
  };
783
787
  function volumeWeight(spanCount) {
784
788
  if (!spanCount || spanCount <= 0) return 0.5;
@@ -831,19 +835,19 @@ function confidenceFromMix(edges, now = Date.now()) {
831
835
  function longestIncomingWalk(graph, start, maxDepth) {
832
836
  let best = { path: [start], edges: [] };
833
837
  const visited = /* @__PURE__ */ new Set([start]);
834
- function step(node, path38, edges) {
835
- if (path38.length > best.path.length) {
836
- best = { path: [...path38], edges: [...edges] };
838
+ function step(node, path39, edges) {
839
+ if (path39.length > best.path.length) {
840
+ best = { path: [...path39], edges: [...edges] };
837
841
  }
838
- if (path38.length - 1 >= maxDepth) return;
842
+ if (path39.length - 1 >= maxDepth) return;
839
843
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
840
844
  for (const [srcId, edge] of incoming) {
841
845
  if (visited.has(srcId)) continue;
842
846
  visited.add(srcId);
843
- path38.push(srcId);
847
+ path39.push(srcId);
844
848
  edges.push(edge);
845
- step(srcId, path38, edges);
846
- path38.pop();
849
+ step(srcId, path39, edges);
850
+ path39.pop();
847
851
  edges.pop();
848
852
  visited.delete(srcId);
849
853
  }
@@ -1062,8 +1066,8 @@ var evaluateStructural = ({
1062
1066
  for (const edgeId of graph.outboundEdges(id)) {
1063
1067
  const e = graph.getEdgeAttributes(edgeId);
1064
1068
  if (e.type !== rule.edgeType) continue;
1065
- if (e.provenance === import_types2.Provenance.FRONTIER) continue;
1066
1069
  const target = graph.getNodeAttributes(e.target);
1070
+ if (target.type === import_types2.NodeType.FrontierNode) continue;
1067
1071
  if (target.type === rule.toNodeType) {
1068
1072
  satisfied = true;
1069
1073
  break;
@@ -1182,8 +1186,8 @@ var evaluateCompatibility = ({
1182
1186
  for (const edgeId of graph.outboundEdges(svcId)) {
1183
1187
  const e = graph.getEdgeAttributes(edgeId);
1184
1188
  if (e.type !== import_types2.EdgeType.CONNECTS_TO) continue;
1185
- if (e.provenance === import_types2.Provenance.FRONTIER) continue;
1186
1189
  const dbAttrs = graph.getNodeAttributes(e.target);
1190
+ if (dbAttrs.type === import_types2.NodeType.FrontierNode) continue;
1187
1191
  if (dbAttrs.type !== import_types2.NodeType.DatabaseNode) continue;
1188
1192
  const db = dbAttrs;
1189
1193
  for (const pair of compatPairs()) {
@@ -1478,31 +1482,6 @@ function ensureFrontierNode(graph, host, ts) {
1478
1482
  graph.addNode(id, node);
1479
1483
  return id;
1480
1484
  }
1481
- function upsertFrontierEdge(graph, type, source, target, ts) {
1482
- const id = (0, import_types3.frontierEdgeId)(source, target, type);
1483
- if (graph.hasEdge(id)) {
1484
- const existing = graph.getEdgeAttributes(id);
1485
- const updated = {
1486
- ...existing,
1487
- provenance: import_types3.Provenance.FRONTIER,
1488
- lastObserved: ts,
1489
- callCount: (existing.callCount ?? 0) + 1
1490
- };
1491
- graph.replaceEdgeAttributes(id, updated);
1492
- return;
1493
- }
1494
- const edge = {
1495
- id,
1496
- source,
1497
- target,
1498
- type,
1499
- provenance: import_types3.Provenance.FRONTIER,
1500
- confidence: 1,
1501
- lastObserved: ts,
1502
- callCount: 1
1503
- };
1504
- graph.addEdgeWithKey(id, source, target, edge);
1505
- }
1506
1485
  function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1507
1486
  if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
1508
1487
  const id = makeObservedEdgeId(type, source, target);
@@ -1510,35 +1489,37 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1510
1489
  const existing = graph.getEdgeAttributes(id);
1511
1490
  const newSpanCount = (existing.signal?.spanCount ?? existing.callCount ?? 0) + 1;
1512
1491
  const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0);
1492
+ const newSignal = {
1493
+ spanCount: newSpanCount,
1494
+ errorCount: newErrorCount,
1495
+ lastObservedAgeMs: 0
1496
+ };
1513
1497
  const updated = {
1514
1498
  ...existing,
1515
1499
  provenance: import_types3.Provenance.OBSERVED,
1516
1500
  lastObserved: ts,
1517
1501
  callCount: newSpanCount,
1518
- signal: {
1519
- spanCount: newSpanCount,
1520
- errorCount: newErrorCount,
1521
- lastObservedAgeMs: 0
1522
- },
1523
- confidence: 1
1502
+ signal: newSignal,
1503
+ confidence: (0, import_types3.confidenceForObservedSignal)(newSignal)
1524
1504
  };
1525
1505
  graph.replaceEdgeAttributes(id, updated);
1526
1506
  return { edge: updated, created: false };
1527
1507
  }
1508
+ const signal = {
1509
+ spanCount: 1,
1510
+ errorCount: isError ? 1 : 0,
1511
+ lastObservedAgeMs: 0
1512
+ };
1528
1513
  const edge = {
1529
1514
  id,
1530
1515
  source,
1531
1516
  target,
1532
1517
  type,
1533
1518
  provenance: import_types3.Provenance.OBSERVED,
1534
- confidence: 1,
1519
+ confidence: (0, import_types3.confidenceForObservedSignal)(signal),
1535
1520
  lastObserved: ts,
1536
1521
  callCount: 1,
1537
- signal: {
1538
- spanCount: 1,
1539
- errorCount: isError ? 1 : 0,
1540
- lastObservedAgeMs: 0
1541
- }
1522
+ signal
1542
1523
  };
1543
1524
  graph.addEdgeWithKey(id, source, target, edge);
1544
1525
  return { edge, created: true };
@@ -1586,18 +1567,28 @@ async function appendErrorEvent(ctx, ev) {
1586
1567
  await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(ctx.errorsPath), { recursive: true });
1587
1568
  await import_node_fs3.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
1588
1569
  }
1570
+ function sanitizeAttributes(attrs) {
1571
+ const out = {};
1572
+ for (const [k, v] of Object.entries(attrs)) {
1573
+ if (typeof v === "bigint") out[k] = v.toString();
1574
+ else out[k] = v;
1575
+ }
1576
+ return out;
1577
+ }
1589
1578
  function buildErrorEventForReceiver(span) {
1590
1579
  if (span.statusCode !== 2) return null;
1591
1580
  const ts = span.startTimeIso ?? (/* @__PURE__ */ new Date()).toISOString();
1581
+ const attrs = sanitizeAttributes(span.attributes);
1592
1582
  return {
1593
1583
  id: `${span.traceId}:${span.spanId}`,
1594
1584
  timestamp: ts,
1595
1585
  service: span.service,
1596
1586
  traceId: span.traceId,
1597
1587
  spanId: span.spanId,
1598
- errorMessage: span.exception?.message ?? span.errorMessage ?? span.name ?? "unknown error",
1588
+ errorMessage: span.exception?.message ?? span.name ?? "unknown error",
1599
1589
  ...span.exception?.type ? { exceptionType: span.exception.type } : {},
1600
1590
  ...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
1591
+ ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
1601
1592
  affectedNode: (0, import_types3.serviceId)(span.service)
1602
1593
  };
1603
1594
  }
@@ -1648,11 +1639,16 @@ async function handleSpan(ctx, span) {
1648
1639
  affectedNode = targetId;
1649
1640
  resolvedViaAddress = true;
1650
1641
  } else if (!targetId) {
1651
- const frontierId2 = ensureFrontierNode(ctx.graph, host, ts);
1652
- if (ctx.graph.hasNode(sourceId)) {
1653
- upsertFrontierEdge(ctx.graph, import_types3.EdgeType.CALLS, sourceId, frontierId2, ts);
1654
- }
1655
- affectedNode = frontierId2;
1642
+ const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
1643
+ upsertObservedEdge(
1644
+ ctx.graph,
1645
+ import_types3.EdgeType.CALLS,
1646
+ sourceId,
1647
+ frontierNodeId,
1648
+ ts,
1649
+ isError
1650
+ );
1651
+ affectedNode = frontierNodeId;
1656
1652
  resolvedViaAddress = true;
1657
1653
  }
1658
1654
  }
@@ -1674,15 +1670,17 @@ async function handleSpan(ctx, span) {
1674
1670
  if (span.statusCode === 2) {
1675
1671
  stitchTrace(ctx.graph, sourceId, ts);
1676
1672
  if (ctx.writeErrorEventInline !== false) {
1673
+ const attrs = sanitizeAttributes(span.attributes);
1677
1674
  const ev = {
1678
1675
  id: `${span.traceId}:${span.spanId}`,
1679
1676
  timestamp: ts,
1680
1677
  service: span.service,
1681
1678
  traceId: span.traceId,
1682
1679
  spanId: span.spanId,
1683
- errorMessage: span.exception?.message ?? span.errorMessage ?? span.name ?? "unknown error",
1680
+ errorMessage: span.exception?.message ?? span.name ?? "unknown error",
1684
1681
  ...span.exception?.type ? { exceptionType: span.exception.type } : {},
1685
1682
  ...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
1683
+ ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
1686
1684
  affectedNode
1687
1685
  };
1688
1686
  await appendErrorEvent(ctx, ev);
@@ -1738,8 +1736,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId3) {
1738
1736
  }
1739
1737
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
1740
1738
  graph.dropEdge(oldEdgeId);
1741
- const promotedProvenance = edge.provenance === import_types3.Provenance.FRONTIER ? import_types3.Provenance.OBSERVED : edge.provenance;
1742
- const newId = promotedProvenance === import_types3.Provenance.OBSERVED ? (0, import_types3.observedEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types3.Provenance.INFERRED ? (0, import_types3.inferredEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types3.Provenance.EXTRACTED ? (0, import_types3.extractedEdgeId)(newSource, newTarget, edge.type) : (0, import_types3.frontierEdgeId)(newSource, newTarget, edge.type);
1739
+ const newId = edge.provenance === import_types3.Provenance.OBSERVED ? (0, import_types3.observedEdgeId)(newSource, newTarget, edge.type) : edge.provenance === import_types3.Provenance.INFERRED ? (0, import_types3.inferredEdgeId)(newSource, newTarget, edge.type) : (0, import_types3.extractedEdgeId)(newSource, newTarget, edge.type);
1743
1740
  if (graph.hasEdge(newId)) {
1744
1741
  const existing = graph.getEdgeAttributes(newId);
1745
1742
  const merged = {
@@ -1754,8 +1751,7 @@ function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
1754
1751
  ...edge,
1755
1752
  id: newId,
1756
1753
  source: newSource,
1757
- target: newTarget,
1758
- provenance: promotedProvenance
1754
+ target: newTarget
1759
1755
  };
1760
1756
  graph.addEdgeWithKey(newId, newSource, newTarget, rebuilt);
1761
1757
  }
@@ -2092,6 +2088,23 @@ async function writeExtractionErrors(errors, errorsPath) {
2092
2088
  const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
2093
2089
  await import_node_fs7.promises.appendFile(errorsPath, lines, "utf8");
2094
2090
  }
2091
+ var droppedSink = [];
2092
+ function noteExtractedDropped(edge) {
2093
+ droppedSink.push(edge);
2094
+ }
2095
+ function drainDroppedExtracted() {
2096
+ return droppedSink.splice(0, droppedSink.length);
2097
+ }
2098
+ function isRejectedLogEnabled() {
2099
+ const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG;
2100
+ return raw === "1" || raw === "true";
2101
+ }
2102
+ async function writeRejectedExtracted(drops, rejectedPath) {
2103
+ if (drops.length === 0) return;
2104
+ await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(rejectedPath), { recursive: true });
2105
+ const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
2106
+ await import_node_fs7.promises.appendFile(rejectedPath, lines, "utf8");
2107
+ }
2095
2108
 
2096
2109
  // src/extract/services.ts
2097
2110
  var DEFAULT_SCAN_DEPTH = 5;
@@ -3030,6 +3043,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3030
3043
  target: dbNode.id,
3031
3044
  type: import_types7.EdgeType.CONNECTS_TO,
3032
3045
  provenance: import_types7.Provenance.EXTRACTED,
3046
+ confidence: (0, import_types7.confidenceForExtracted)("structural"),
3033
3047
  // ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
3034
3048
  // Ghost-edge cleanup keys retirement on this; the conditional
3035
3049
  // sourceFile spread that used to live here was a v0.1.x leftover.
@@ -3104,6 +3118,7 @@ async function addConfigNodes(graph, services, scanPath) {
3104
3118
  target: node.id,
3105
3119
  type: import_types8.EdgeType.CONFIGURED_BY,
3106
3120
  provenance: import_types8.Provenance.EXTRACTED,
3121
+ confidence: (0, import_types8.confidenceForExtracted)("structural"),
3107
3122
  evidence: { file: relPath.split(import_node_path18.default.sep).join("/") }
3108
3123
  };
3109
3124
  if (!graph.hasEdge(edge.id)) {
@@ -3258,17 +3273,32 @@ async function addHttpCallEdges(graph, services) {
3258
3273
  for (const [targetId, evidenceFile] of seenTargets) {
3259
3274
  const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
3260
3275
  const line = lineOf(fileContent, `//${evidenceFile.host}`);
3276
+ const confidence = (0, import_types9.confidenceForExtracted)("hostname-shape-match");
3277
+ const ev = {
3278
+ file: import_node_path20.default.relative(service.dir, evidenceFile.file),
3279
+ line,
3280
+ snippet: snippet(fileContent, line)
3281
+ };
3282
+ const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, targetId, import_types9.EdgeType.CALLS);
3283
+ if (!(0, import_types9.passesExtractedFloor)(confidence)) {
3284
+ noteExtractedDropped({
3285
+ source: service.node.id,
3286
+ target: targetId,
3287
+ type: import_types9.EdgeType.CALLS,
3288
+ confidence,
3289
+ confidenceKind: "hostname-shape-match",
3290
+ evidence: ev
3291
+ });
3292
+ continue;
3293
+ }
3261
3294
  const edge = {
3262
- id: (0, import_types4.extractedEdgeId)(service.node.id, targetId, import_types9.EdgeType.CALLS),
3295
+ id: edgeId,
3263
3296
  source: service.node.id,
3264
3297
  target: targetId,
3265
3298
  type: import_types9.EdgeType.CALLS,
3266
3299
  provenance: import_types9.Provenance.EXTRACTED,
3267
- evidence: {
3268
- file: import_node_path20.default.relative(service.dir, evidenceFile.file),
3269
- line,
3270
- snippet: snippet(fileContent, line)
3271
- }
3300
+ confidence,
3301
+ evidence: ev
3272
3302
  };
3273
3303
  if (!graph.hasEdge(edge.id)) {
3274
3304
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3307,6 +3337,10 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3307
3337
  name: topic,
3308
3338
  kind: "kafka-topic",
3309
3339
  edgeType,
3340
+ // `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})` —
3341
+ // framework-aware (kafkajs / node-rdkafka shape). Verified-call-site
3342
+ // tier (ADR-066).
3343
+ confidenceKind: "verified-call-site",
3310
3344
  evidence: {
3311
3345
  file: import_node_path21.default.relative(serviceDir, file.path),
3312
3346
  line,
@@ -3339,6 +3373,10 @@ function redisEndpointsFromFile(file, serviceDir) {
3339
3373
  name: host,
3340
3374
  kind: "redis",
3341
3375
  edgeType: "CALLS",
3376
+ // `redis://host` URL literal — the scheme is structural support, but no
3377
+ // call expression is verified to wire it through. URL-with-structural-
3378
+ // support tier (ADR-066).
3379
+ confidenceKind: "url-with-structural-support",
3342
3380
  evidence: {
3343
3381
  file: import_node_path22.default.relative(serviceDir, file.path),
3344
3382
  line,
@@ -3380,6 +3418,10 @@ function awsEndpointsFromFile(file, serviceDir) {
3380
3418
  name,
3381
3419
  kind,
3382
3420
  edgeType: "CALLS",
3421
+ // SDK marker (S3Client, GetCommand, etc.) plus a Bucket/TableName
3422
+ // literal — framework-aware recognizer, verified-call-site tier
3423
+ // (ADR-066).
3424
+ confidenceKind: "verified-call-site",
3383
3425
  evidence: {
3384
3426
  file: import_node_path23.default.relative(serviceDir, file.path),
3385
3427
  line,
@@ -3459,6 +3501,10 @@ function grpcEndpointsFromFile(file, serviceDir) {
3459
3501
  name,
3460
3502
  kind,
3461
3503
  edgeType: "CALLS",
3504
+ // `new <Name>Client(...)` with @aws-sdk/* or @grpc/grpc-js import
3505
+ // context — import-aware classification per #238. Verified-call-site
3506
+ // tier (ADR-066).
3507
+ confidenceKind: "verified-call-site",
3462
3508
  evidence: {
3463
3509
  file: import_node_path24.default.relative(serviceDir, file.path),
3464
3510
  line,
@@ -3519,6 +3565,18 @@ async function addExternalEndpointEdges(graph, services) {
3519
3565
  const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, ep.infraId, edgeType);
3520
3566
  if (seenEdges.has(edgeId)) continue;
3521
3567
  seenEdges.add(edgeId);
3568
+ const confidence = (0, import_types14.confidenceForExtracted)(ep.confidenceKind);
3569
+ if (!(0, import_types14.passesExtractedFloor)(confidence)) {
3570
+ noteExtractedDropped({
3571
+ source: service.node.id,
3572
+ target: ep.infraId,
3573
+ type: edgeType,
3574
+ confidence,
3575
+ confidenceKind: ep.confidenceKind,
3576
+ evidence: ep.evidence
3577
+ });
3578
+ continue;
3579
+ }
3522
3580
  if (!graph.hasEdge(edgeId)) {
3523
3581
  const edge = {
3524
3582
  id: edgeId,
@@ -3526,6 +3584,7 @@ async function addExternalEndpointEdges(graph, services) {
3526
3584
  target: ep.infraId,
3527
3585
  type: edgeType,
3528
3586
  provenance: import_types14.Provenance.EXTRACTED,
3587
+ confidence,
3529
3588
  evidence: ep.evidence
3530
3589
  };
3531
3590
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3645,6 +3704,7 @@ async function addComposeInfra(graph, scanPath, services) {
3645
3704
  target: targetId,
3646
3705
  type: import_types16.EdgeType.DEPENDS_ON,
3647
3706
  provenance: import_types16.Provenance.EXTRACTED,
3707
+ confidence: (0, import_types16.confidenceForExtracted)("structural"),
3648
3708
  evidence: { file: evidenceFile }
3649
3709
  };
3650
3710
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3705,6 +3765,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3705
3765
  target: node.id,
3706
3766
  type: import_types17.EdgeType.RUNS_ON,
3707
3767
  provenance: import_types17.Provenance.EXTRACTED,
3768
+ confidence: (0, import_types17.confidenceForExtracted)("structural"),
3708
3769
  evidence: {
3709
3770
  file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
3710
3771
  }
@@ -3821,6 +3882,9 @@ async function addInfra(graph, scanPath, services) {
3821
3882
  };
3822
3883
  }
3823
3884
 
3885
+ // src/extract/index.ts
3886
+ var import_node_path30 = __toESM(require("path"), 1);
3887
+
3824
3888
  // src/extract/retire.ts
3825
3889
  init_cjs_shims();
3826
3890
  var import_node_fs17 = require("fs");
@@ -3873,13 +3937,26 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
3873
3937
  );
3874
3938
  }
3875
3939
  }
3940
+ const droppedEntries = drainDroppedExtracted();
3941
+ if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
3942
+ const rejectedPath = import_node_path30.default.join(import_node_path30.default.dirname(opts.errorsPath), "rejected.ndjson");
3943
+ try {
3944
+ await writeRejectedExtracted(droppedEntries, rejectedPath);
3945
+ } catch (err) {
3946
+ console.warn(
3947
+ `[neat] failed to write rejected extracted edges to ${rejectedPath}: ${err.message}`
3948
+ );
3949
+ }
3950
+ }
3876
3951
  const result = {
3877
3952
  nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
3878
3953
  edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
3879
3954
  frontiersPromoted,
3880
3955
  extractionErrors: errorEntries.length,
3881
3956
  errorEntries,
3882
- ghostsRetired
3957
+ ghostsRetired,
3958
+ extractedDropped: droppedEntries.length,
3959
+ droppedEntries
3883
3960
  };
3884
3961
  emitNeatEvent({
3885
3962
  type: "extraction-complete",
@@ -3897,8 +3974,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
3897
3974
  // src/persist.ts
3898
3975
  init_cjs_shims();
3899
3976
  var import_node_fs18 = require("fs");
3900
- var import_node_path30 = __toESM(require("path"), 1);
3901
- var SCHEMA_VERSION = 2;
3977
+ var import_node_path31 = __toESM(require("path"), 1);
3978
+ var import_types19 = require("@neat.is/types");
3979
+ var SCHEMA_VERSION = 3;
3902
3980
  function migrateV1ToV2(payload) {
3903
3981
  const nodes = payload.graph.nodes;
3904
3982
  if (Array.isArray(nodes)) {
@@ -3910,8 +3988,27 @@ function migrateV1ToV2(payload) {
3910
3988
  }
3911
3989
  return { ...payload, schemaVersion: 2 };
3912
3990
  }
3991
+ function migrateV2ToV3(payload) {
3992
+ const edges = payload.graph.edges;
3993
+ if (Array.isArray(edges)) {
3994
+ for (const edge of edges) {
3995
+ const attrs = edge.attributes;
3996
+ if (!attrs || attrs.provenance !== "FRONTIER") continue;
3997
+ attrs.provenance = import_types19.Provenance.OBSERVED;
3998
+ const type = typeof attrs.type === "string" ? attrs.type : void 0;
3999
+ const source = typeof attrs.source === "string" ? attrs.source : void 0;
4000
+ const target = typeof attrs.target === "string" ? attrs.target : void 0;
4001
+ if (type && source && target) {
4002
+ const newId = (0, import_types19.observedEdgeId)(source, target, type);
4003
+ attrs.id = newId;
4004
+ if (edge.key) edge.key = newId;
4005
+ }
4006
+ }
4007
+ }
4008
+ return { ...payload, schemaVersion: 3 };
4009
+ }
3913
4010
  async function ensureDir(filePath) {
3914
- await import_node_fs18.promises.mkdir(import_node_path30.default.dirname(filePath), { recursive: true });
4011
+ await import_node_fs18.promises.mkdir(import_node_path31.default.dirname(filePath), { recursive: true });
3915
4012
  }
3916
4013
  async function saveGraphToDisk(graph, outPath) {
3917
4014
  await ensureDir(outPath);
@@ -3936,6 +4033,9 @@ async function loadGraphFromDisk(graph, outPath) {
3936
4033
  if (payload.schemaVersion === 1) {
3937
4034
  payload = migrateV1ToV2(payload);
3938
4035
  }
4036
+ if (payload.schemaVersion === 2) {
4037
+ payload = migrateV2ToV3(payload);
4038
+ }
3939
4039
  if (payload.schemaVersion !== SCHEMA_VERSION) {
3940
4040
  throw new Error(
3941
4041
  `persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`
@@ -3980,23 +4080,23 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
3980
4080
 
3981
4081
  // src/projects.ts
3982
4082
  init_cjs_shims();
3983
- var import_node_path31 = __toESM(require("path"), 1);
4083
+ var import_node_path32 = __toESM(require("path"), 1);
3984
4084
  function pathsForProject(project, baseDir) {
3985
4085
  if (project === DEFAULT_PROJECT) {
3986
4086
  return {
3987
- snapshotPath: import_node_path31.default.join(baseDir, "graph.json"),
3988
- errorsPath: import_node_path31.default.join(baseDir, "errors.ndjson"),
3989
- staleEventsPath: import_node_path31.default.join(baseDir, "stale-events.ndjson"),
3990
- embeddingsCachePath: import_node_path31.default.join(baseDir, "embeddings.json"),
3991
- policyViolationsPath: import_node_path31.default.join(baseDir, "policy-violations.ndjson")
4087
+ snapshotPath: import_node_path32.default.join(baseDir, "graph.json"),
4088
+ errorsPath: import_node_path32.default.join(baseDir, "errors.ndjson"),
4089
+ staleEventsPath: import_node_path32.default.join(baseDir, "stale-events.ndjson"),
4090
+ embeddingsCachePath: import_node_path32.default.join(baseDir, "embeddings.json"),
4091
+ policyViolationsPath: import_node_path32.default.join(baseDir, "policy-violations.ndjson")
3992
4092
  };
3993
4093
  }
3994
4094
  return {
3995
- snapshotPath: import_node_path31.default.join(baseDir, `${project}.json`),
3996
- errorsPath: import_node_path31.default.join(baseDir, `errors.${project}.ndjson`),
3997
- staleEventsPath: import_node_path31.default.join(baseDir, `stale-events.${project}.ndjson`),
3998
- embeddingsCachePath: import_node_path31.default.join(baseDir, `embeddings.${project}.json`),
3999
- policyViolationsPath: import_node_path31.default.join(baseDir, `policy-violations.${project}.ndjson`)
4095
+ snapshotPath: import_node_path32.default.join(baseDir, `${project}.json`),
4096
+ errorsPath: import_node_path32.default.join(baseDir, `errors.${project}.ndjson`),
4097
+ staleEventsPath: import_node_path32.default.join(baseDir, `stale-events.${project}.ndjson`),
4098
+ embeddingsCachePath: import_node_path32.default.join(baseDir, `embeddings.${project}.json`),
4099
+ policyViolationsPath: import_node_path32.default.join(baseDir, `policy-violations.${project}.ndjson`)
4000
4100
  };
4001
4101
  }
4002
4102
  var Projects = class {
@@ -4034,11 +4134,11 @@ var Projects = class {
4034
4134
  init_cjs_shims();
4035
4135
  var import_fastify = __toESM(require("fastify"), 1);
4036
4136
  var import_cors = __toESM(require("@fastify/cors"), 1);
4037
- var import_types21 = require("@neat.is/types");
4137
+ var import_types22 = require("@neat.is/types");
4038
4138
 
4039
4139
  // src/divergences.ts
4040
4140
  init_cjs_shims();
4041
- var import_types19 = require("@neat.is/types");
4141
+ var import_types20 = require("@neat.is/types");
4042
4142
  function bucketKey(source, target, type) {
4043
4143
  return `${type}|${source}|${target}`;
4044
4144
  }
@@ -4046,25 +4146,22 @@ function bucketEdges(graph) {
4046
4146
  const buckets = /* @__PURE__ */ new Map();
4047
4147
  graph.forEachEdge((id, attrs) => {
4048
4148
  const e = attrs;
4049
- const parsed = (0, import_types19.parseEdgeId)(id);
4149
+ const parsed = (0, import_types20.parseEdgeId)(id);
4050
4150
  const provenance = parsed?.provenance ?? e.provenance;
4051
4151
  const key = bucketKey(e.source, e.target, e.type);
4052
4152
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
4053
4153
  switch (provenance) {
4054
- case import_types19.Provenance.EXTRACTED:
4154
+ case import_types20.Provenance.EXTRACTED:
4055
4155
  cur.extracted = e;
4056
4156
  break;
4057
- case import_types19.Provenance.OBSERVED:
4157
+ case import_types20.Provenance.OBSERVED:
4058
4158
  cur.observed = e;
4059
4159
  break;
4060
- case import_types19.Provenance.INFERRED:
4160
+ case import_types20.Provenance.INFERRED:
4061
4161
  cur.inferred = e;
4062
4162
  break;
4063
- case import_types19.Provenance.FRONTIER:
4064
- cur.frontier = e;
4065
- break;
4066
4163
  default:
4067
- if (e.provenance === import_types19.Provenance.STALE) cur.stale = e;
4164
+ if (e.provenance === import_types20.Provenance.STALE) cur.stale = e;
4068
4165
  }
4069
4166
  buckets.set(key, cur);
4070
4167
  });
@@ -4073,15 +4170,7 @@ function bucketEdges(graph) {
4073
4170
  function nodeIsFrontier(graph, nodeId) {
4074
4171
  if (!graph.hasNode(nodeId)) return false;
4075
4172
  const attrs = graph.getNodeAttributes(nodeId);
4076
- return attrs.type === import_types19.NodeType.FrontierNode;
4077
- }
4078
- function hasAnyObservedFromSource(graph, sourceId) {
4079
- if (!graph.hasNode(sourceId)) return false;
4080
- for (const edgeId of graph.outboundEdges(sourceId)) {
4081
- const e = graph.getEdgeAttributes(edgeId);
4082
- if (e.provenance === import_types19.Provenance.OBSERVED) return true;
4083
- }
4084
- return false;
4173
+ return attrs.type === import_types20.NodeType.FrontierNode;
4085
4174
  }
4086
4175
  function clampConfidence(n) {
4087
4176
  if (!Number.isFinite(n)) return 0;
@@ -4096,32 +4185,34 @@ function reasonForMissingExtracted(source, target, type) {
4096
4185
  var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
4097
4186
  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.";
4098
4187
  var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
4188
+ function gradedConfidence(edge) {
4189
+ if (typeof edge.confidence === "number") return clampConfidence(edge.confidence);
4190
+ return clampConfidence(confidenceForEdge(edge));
4191
+ }
4099
4192
  function detectMissingDivergences(graph, bucket) {
4100
4193
  const out = [];
4101
4194
  if (bucket.extracted && !bucket.observed) {
4102
4195
  if (!nodeIsFrontier(graph, bucket.target)) {
4103
- const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
4104
4196
  out.push({
4105
4197
  type: "missing-observed",
4106
4198
  source: bucket.source,
4107
4199
  target: bucket.target,
4108
4200
  edgeType: bucket.type,
4109
4201
  extracted: bucket.extracted,
4110
- confidence: sourceHasTraffic ? 1 : 0.5,
4202
+ confidence: gradedConfidence(bucket.extracted),
4111
4203
  reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
4112
4204
  recommendation: RECOMMENDATION_MISSING_OBSERVED
4113
4205
  });
4114
4206
  }
4115
4207
  }
4116
4208
  if (bucket.observed && !bucket.extracted) {
4117
- const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
4118
4209
  out.push({
4119
4210
  type: "missing-extracted",
4120
4211
  source: bucket.source,
4121
4212
  target: bucket.target,
4122
4213
  edgeType: bucket.type,
4123
4214
  observed: bucket.observed,
4124
- confidence: cascaded,
4215
+ confidence: gradedConfidence(bucket.observed),
4125
4216
  reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
4126
4217
  recommendation: RECOMMENDATION_MISSING_EXTRACTED
4127
4218
  });
@@ -4140,7 +4231,7 @@ function declaredHostFor(svc) {
4140
4231
  function hasExtractedConfiguredBy(graph, svcId) {
4141
4232
  for (const edgeId of graph.outboundEdges(svcId)) {
4142
4233
  const e = graph.getEdgeAttributes(edgeId);
4143
- if (e.type === import_types19.EdgeType.CONFIGURED_BY && e.provenance === import_types19.Provenance.EXTRACTED) {
4234
+ if (e.type === import_types20.EdgeType.CONFIGURED_BY && e.provenance === import_types20.Provenance.EXTRACTED) {
4144
4235
  return true;
4145
4236
  }
4146
4237
  }
@@ -4153,10 +4244,10 @@ function detectHostMismatch(graph, svcId, svc) {
4153
4244
  const out = [];
4154
4245
  for (const edgeId of graph.outboundEdges(svcId)) {
4155
4246
  const edge = graph.getEdgeAttributes(edgeId);
4156
- if (edge.type !== import_types19.EdgeType.CONNECTS_TO) continue;
4157
- if (edge.provenance !== import_types19.Provenance.OBSERVED) continue;
4247
+ if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
4248
+ if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
4158
4249
  const target = graph.getNodeAttributes(edge.target);
4159
- if (target.type !== import_types19.NodeType.DatabaseNode) continue;
4250
+ if (target.type !== import_types20.NodeType.DatabaseNode) continue;
4160
4251
  const observedHost = target.host?.trim();
4161
4252
  if (!observedHost) continue;
4162
4253
  if (observedHost === declaredHost) continue;
@@ -4178,10 +4269,10 @@ function detectCompatDivergences(graph, svcId, svc) {
4178
4269
  const deps = svc.dependencies ?? {};
4179
4270
  for (const edgeId of graph.outboundEdges(svcId)) {
4180
4271
  const edge = graph.getEdgeAttributes(edgeId);
4181
- if (edge.type !== import_types19.EdgeType.CONNECTS_TO) continue;
4182
- if (edge.provenance !== import_types19.Provenance.OBSERVED) continue;
4272
+ if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
4273
+ if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
4183
4274
  const target = graph.getNodeAttributes(edge.target);
4184
- if (target.type !== import_types19.NodeType.DatabaseNode) continue;
4275
+ if (target.type !== import_types20.NodeType.DatabaseNode) continue;
4185
4276
  for (const pair of compatPairs()) {
4186
4277
  if (pair.engine !== target.engine) continue;
4187
4278
  const declared = deps[pair.driver];
@@ -4242,7 +4333,7 @@ function computeDivergences(graph, opts = {}) {
4242
4333
  }
4243
4334
  graph.forEachNode((nodeId, attrs) => {
4244
4335
  const n = attrs;
4245
- if (n.type !== import_types19.NodeType.ServiceNode) return;
4336
+ if (n.type !== import_types20.NodeType.ServiceNode) return;
4246
4337
  const svc = n;
4247
4338
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
4248
4339
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -4260,13 +4351,22 @@ function computeDivergences(graph, opts = {}) {
4260
4351
  const target = opts.node;
4261
4352
  filtered = filtered.filter((d) => involvesNode(d, target));
4262
4353
  }
4354
+ const TYPE_LEADERSHIP = {
4355
+ "missing-extracted": 0,
4356
+ "missing-observed": 1,
4357
+ "version-mismatch": 2,
4358
+ "host-mismatch": 3,
4359
+ "compat-violation": 4
4360
+ };
4263
4361
  filtered.sort((a, b) => {
4264
4362
  if (b.confidence !== a.confidence) return b.confidence - a.confidence;
4363
+ const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type];
4364
+ if (lead !== 0) return lead;
4265
4365
  if (a.type !== b.type) return a.type.localeCompare(b.type);
4266
4366
  if (a.source !== b.source) return a.source.localeCompare(b.source);
4267
4367
  return a.target.localeCompare(b.target);
4268
4368
  });
4269
- return import_types19.DivergenceResultSchema.parse({
4369
+ return import_types20.DivergenceResultSchema.parse({
4270
4370
  divergences: filtered,
4271
4371
  totalAffected: filtered.length,
4272
4372
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4354,23 +4454,23 @@ function canonicalJson(value) {
4354
4454
  init_cjs_shims();
4355
4455
  var import_node_fs20 = require("fs");
4356
4456
  var import_node_os2 = __toESM(require("os"), 1);
4357
- var import_node_path32 = __toESM(require("path"), 1);
4358
- var import_types20 = require("@neat.is/types");
4457
+ var import_node_path33 = __toESM(require("path"), 1);
4458
+ var import_types21 = require("@neat.is/types");
4359
4459
  var LOCK_TIMEOUT_MS = 5e3;
4360
4460
  var LOCK_RETRY_MS = 50;
4361
4461
  function neatHome() {
4362
4462
  const override = process.env.NEAT_HOME;
4363
- if (override && override.length > 0) return import_node_path32.default.resolve(override);
4364
- return import_node_path32.default.join(import_node_os2.default.homedir(), ".neat");
4463
+ if (override && override.length > 0) return import_node_path33.default.resolve(override);
4464
+ return import_node_path33.default.join(import_node_os2.default.homedir(), ".neat");
4365
4465
  }
4366
4466
  function registryPath() {
4367
- return import_node_path32.default.join(neatHome(), "projects.json");
4467
+ return import_node_path33.default.join(neatHome(), "projects.json");
4368
4468
  }
4369
4469
  function registryLockPath() {
4370
- return import_node_path32.default.join(neatHome(), "projects.json.lock");
4470
+ return import_node_path33.default.join(neatHome(), "projects.json.lock");
4371
4471
  }
4372
4472
  async function writeAtomically(target, contents) {
4373
- await import_node_fs20.promises.mkdir(import_node_path32.default.dirname(target), { recursive: true });
4473
+ await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(target), { recursive: true });
4374
4474
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
4375
4475
  const fd = await import_node_fs20.promises.open(tmp, "w");
4376
4476
  try {
@@ -4383,7 +4483,7 @@ async function writeAtomically(target, contents) {
4383
4483
  }
4384
4484
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
4385
4485
  const deadline = Date.now() + timeoutMs;
4386
- await import_node_fs20.promises.mkdir(import_node_path32.default.dirname(lockPath), { recursive: true });
4486
+ await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(lockPath), { recursive: true });
4387
4487
  while (true) {
4388
4488
  try {
4389
4489
  const fd = await import_node_fs20.promises.open(lockPath, "wx");
@@ -4426,10 +4526,10 @@ async function readRegistry() {
4426
4526
  throw err;
4427
4527
  }
4428
4528
  const parsed = JSON.parse(raw);
4429
- return import_types20.RegistryFileSchema.parse(parsed);
4529
+ return import_types21.RegistryFileSchema.parse(parsed);
4430
4530
  }
4431
4531
  async function writeRegistry(reg) {
4432
- const validated = import_types20.RegistryFileSchema.parse(reg);
4532
+ const validated = import_types21.RegistryFileSchema.parse(reg);
4433
4533
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
4434
4534
  }
4435
4535
  async function getProject(name) {
@@ -4639,11 +4739,11 @@ function registerRoutes(scope, ctx) {
4639
4739
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
4640
4740
  const parsed = [];
4641
4741
  for (const c of candidates) {
4642
- const r = import_types21.DivergenceTypeSchema.safeParse(c);
4742
+ const r = import_types22.DivergenceTypeSchema.safeParse(c);
4643
4743
  if (!r.success) {
4644
4744
  return reply.code(400).send({
4645
4745
  error: `unknown divergence type "${c}"`,
4646
- allowed: import_types21.DivergenceTypeSchema.options
4746
+ allowed: import_types22.DivergenceTypeSchema.options
4647
4747
  });
4648
4748
  }
4649
4749
  parsed.push(r.data);
@@ -4827,7 +4927,7 @@ function registerRoutes(scope, ctx) {
4827
4927
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
4828
4928
  let violations = await log.readAll();
4829
4929
  if (req2.query.severity) {
4830
- const sev = import_types21.PolicySeveritySchema.safeParse(req2.query.severity);
4930
+ const sev = import_types22.PolicySeveritySchema.safeParse(req2.query.severity);
4831
4931
  if (!sev.success) {
4832
4932
  return reply.code(400).send({
4833
4933
  error: "invalid severity",
@@ -4844,7 +4944,7 @@ function registerRoutes(scope, ctx) {
4844
4944
  scope.post("/policies/check", async (req2, reply) => {
4845
4945
  const proj = resolveProject(registry, req2, reply);
4846
4946
  if (!proj) return;
4847
- const parsed = import_types21.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
4947
+ const parsed = import_types22.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
4848
4948
  if (!parsed.success) {
4849
4949
  return reply.code(400).send({
4850
4950
  error: "invalid /policies/check body",
@@ -4945,11 +5045,11 @@ async function buildApi(opts) {
4945
5045
  // src/daemon.ts
4946
5046
  init_otel();
4947
5047
  function neatHomeFor(opts) {
4948
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path35.default.resolve(opts.neatHome);
5048
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path36.default.resolve(opts.neatHome);
4949
5049
  const env = process.env.NEAT_HOME;
4950
- if (env && env.length > 0) return import_node_path35.default.resolve(env);
5050
+ if (env && env.length > 0) return import_node_path36.default.resolve(env);
4951
5051
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
4952
- return import_node_path35.default.join(home, ".neat");
5052
+ return import_node_path36.default.join(home, ".neat");
4953
5053
  }
4954
5054
  function routeSpanToProject(serviceName, projects) {
4955
5055
  if (!serviceName) return DEFAULT_PROJECT;
@@ -4962,7 +5062,7 @@ function routeSpanToProject(serviceName, projects) {
4962
5062
  return DEFAULT_PROJECT;
4963
5063
  }
4964
5064
  async function bootstrapProject(entry2) {
4965
- const paths = pathsForProject(entry2.name, import_node_path35.default.join(entry2.path, "neat-out"));
5065
+ const paths = pathsForProject(entry2.name, import_node_path36.default.join(entry2.path, "neat-out"));
4966
5066
  try {
4967
5067
  const stat = await import_node_fs21.promises.stat(entry2.path);
4968
5068
  if (!stat.isDirectory()) {
@@ -5035,7 +5135,7 @@ async function startDaemon(opts = {}) {
5035
5135
  `neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
5036
5136
  );
5037
5137
  }
5038
- const pidPath = import_node_path35.default.join(home, "neatd.pid");
5138
+ const pidPath = import_node_path36.default.join(home, "neatd.pid");
5039
5139
  await writeAtomically(pidPath, `${process.pid}
5040
5140
  `);
5041
5141
  const slots = /* @__PURE__ */ new Map();
@@ -5199,7 +5299,7 @@ async function startDaemon(opts = {}) {
5199
5299
  init_cjs_shims();
5200
5300
  var import_node_child_process = require("child_process");
5201
5301
  var import_node_net = __toESM(require("net"), 1);
5202
- var import_node_path36 = __toESM(require("path"), 1);
5302
+ var import_node_path37 = __toESM(require("path"), 1);
5203
5303
  var DEFAULT_WEB_PORT = 6328;
5204
5304
  async function assertPortFree(port) {
5205
5305
  await new Promise((resolve, reject) => {
@@ -5227,10 +5327,10 @@ function resolveWebPackageDir() {
5227
5327
  eval("require")
5228
5328
  );
5229
5329
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
5230
- return import_node_path36.default.dirname(pkgJsonPath);
5330
+ return import_node_path37.default.dirname(pkgJsonPath);
5231
5331
  }
5232
5332
  function resolveStandaloneServerEntry(webDir) {
5233
- return import_node_path36.default.join(webDir, ".next/standalone/packages/web/server.js");
5333
+ return import_node_path37.default.join(webDir, ".next/standalone/packages/web/server.js");
5234
5334
  }
5235
5335
  async function spawnWebUI(restPort) {
5236
5336
  const portRaw = process.env.NEAT_WEB_PORT;
@@ -5255,7 +5355,7 @@ async function spawnWebUI(restPort) {
5255
5355
  NEAT_API_URL: process.env.NEAT_API_URL ?? `http://localhost:${restPort}`
5256
5356
  };
5257
5357
  const child = (0, import_node_child_process.spawn)(process.execPath, [serverEntry], {
5258
- cwd: import_node_path36.default.dirname(serverEntry),
5358
+ cwd: import_node_path37.default.dirname(serverEntry),
5259
5359
  env,
5260
5360
  stdio: ["ignore", "inherit", "inherit"],
5261
5361
  detached: false
@@ -5292,14 +5392,14 @@ async function spawnWebUI(restPort) {
5292
5392
  // src/neatd.ts
5293
5393
  function neatHome2() {
5294
5394
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
5295
- return import_node_path37.default.resolve(process.env.NEAT_HOME);
5395
+ return import_node_path38.default.resolve(process.env.NEAT_HOME);
5296
5396
  }
5297
5397
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
5298
- return import_node_path37.default.join(home, ".neat");
5398
+ return import_node_path38.default.join(home, ".neat");
5299
5399
  }
5300
5400
  async function readPid() {
5301
5401
  try {
5302
- const raw = await import_node_fs22.promises.readFile(import_node_path37.default.join(neatHome2(), "neatd.pid"), "utf8");
5402
+ const raw = await import_node_fs22.promises.readFile(import_node_path38.default.join(neatHome2(), "neatd.pid"), "utf8");
5303
5403
  const n = Number.parseInt(raw.trim(), 10);
5304
5404
  return Number.isFinite(n) ? n : null;
5305
5405
  } catch {