@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.
@@ -339,17 +339,21 @@ import {
339
339
  BlastRadiusResultSchema,
340
340
  NodeType,
341
341
  PROV_RANK,
342
- Provenance,
343
342
  RootCauseResultSchema,
344
343
  TransitiveDependenciesResultSchema
345
344
  } from "@neat.is/types";
346
345
  var ROOT_CAUSE_MAX_DEPTH = 5;
347
346
  var BLAST_RADIUS_DEFAULT_DEPTH = 10;
347
+ function isFrontierNode(graph, nodeId) {
348
+ if (!graph.hasNode(nodeId)) return false;
349
+ const attrs = graph.getNodeAttributes(nodeId);
350
+ return attrs.type === NodeType.FrontierNode;
351
+ }
348
352
  function bestEdgeBySource(graph, edgeIds) {
349
353
  const best = /* @__PURE__ */ new Map();
350
354
  for (const id of edgeIds) {
351
355
  const e = graph.getEdgeAttributes(id);
352
- if (e.provenance === Provenance.FRONTIER) continue;
356
+ if (isFrontierNode(graph, e.source)) continue;
353
357
  const cur = best.get(e.source);
354
358
  if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {
355
359
  best.set(e.source, e);
@@ -361,7 +365,7 @@ function bestEdgeByTarget(graph, edgeIds) {
361
365
  const best = /* @__PURE__ */ new Map();
362
366
  for (const id of edgeIds) {
363
367
  const e = graph.getEdgeAttributes(id);
364
- if (e.provenance === Provenance.FRONTIER) continue;
368
+ if (isFrontierNode(graph, e.target)) continue;
365
369
  const cur = best.get(e.target);
366
370
  if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {
367
371
  best.set(e.target, e);
@@ -373,8 +377,7 @@ var PROVENANCE_CEILING = {
373
377
  OBSERVED: 1,
374
378
  INFERRED: 0.7,
375
379
  EXTRACTED: 0.5,
376
- STALE: 0.3,
377
- FRONTIER: 0.3
380
+ STALE: 0.3
378
381
  };
379
382
  function volumeWeight(spanCount) {
380
383
  if (!spanCount || spanCount <= 0) return 0.5;
@@ -427,19 +430,19 @@ function confidenceFromMix(edges, now = Date.now()) {
427
430
  function longestIncomingWalk(graph, start, maxDepth) {
428
431
  let best = { path: [start], edges: [] };
429
432
  const visited = /* @__PURE__ */ new Set([start]);
430
- function step(node, path33, edges) {
431
- if (path33.length > best.path.length) {
432
- best = { path: [...path33], edges: [...edges] };
433
+ function step(node, path34, edges) {
434
+ if (path34.length > best.path.length) {
435
+ best = { path: [...path34], edges: [...edges] };
433
436
  }
434
- if (path33.length - 1 >= maxDepth) return;
437
+ if (path34.length - 1 >= maxDepth) return;
435
438
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
436
439
  for (const [srcId, edge] of incoming) {
437
440
  if (visited.has(srcId)) continue;
438
441
  visited.add(srcId);
439
- path33.push(srcId);
442
+ path34.push(srcId);
440
443
  edges.push(edge);
441
- step(srcId, path33, edges);
442
- path33.pop();
444
+ step(srcId, path34, edges);
445
+ path34.pop();
443
446
  edges.pop();
444
447
  visited.delete(srcId);
445
448
  }
@@ -631,8 +634,7 @@ import path2 from "path";
631
634
  import {
632
635
  EdgeType,
633
636
  NodeType as NodeType2,
634
- PolicyFileSchema,
635
- Provenance as Provenance2
637
+ PolicyFileSchema
636
638
  } from "@neat.is/types";
637
639
 
638
640
  // src/events.ts
@@ -733,8 +735,8 @@ var evaluateStructural = ({
733
735
  for (const edgeId of graph.outboundEdges(id)) {
734
736
  const e = graph.getEdgeAttributes(edgeId);
735
737
  if (e.type !== rule.edgeType) continue;
736
- if (e.provenance === Provenance2.FRONTIER) continue;
737
738
  const target = graph.getNodeAttributes(e.target);
739
+ if (target.type === NodeType2.FrontierNode) continue;
738
740
  if (target.type === rule.toNodeType) {
739
741
  satisfied = true;
740
742
  break;
@@ -853,8 +855,8 @@ var evaluateCompatibility = ({
853
855
  for (const edgeId of graph.outboundEdges(svcId)) {
854
856
  const e = graph.getEdgeAttributes(edgeId);
855
857
  if (e.type !== EdgeType.CONNECTS_TO) continue;
856
- if (e.provenance === Provenance2.FRONTIER) continue;
857
858
  const dbAttrs = graph.getNodeAttributes(e.target);
859
+ if (dbAttrs.type === NodeType2.FrontierNode) continue;
858
860
  if (dbAttrs.type !== NodeType2.DatabaseNode) continue;
859
861
  const db = dbAttrs;
860
862
  for (const pair of compatPairs()) {
@@ -1017,10 +1019,10 @@ var PolicyViolationsLog = class {
1017
1019
  import {
1018
1020
  EdgeType as EdgeType2,
1019
1021
  NodeType as NodeType3,
1020
- Provenance as Provenance3,
1022
+ Provenance,
1023
+ confidenceForObservedSignal,
1021
1024
  databaseId,
1022
1025
  extractedEdgeId,
1023
- frontierEdgeId,
1024
1026
  frontierId,
1025
1027
  inferredEdgeId,
1026
1028
  observedEdgeId,
@@ -1182,31 +1184,6 @@ function ensureFrontierNode(graph, host, ts) {
1182
1184
  graph.addNode(id, node);
1183
1185
  return id;
1184
1186
  }
1185
- function upsertFrontierEdge(graph, type, source, target, ts) {
1186
- const id = frontierEdgeId(source, target, type);
1187
- if (graph.hasEdge(id)) {
1188
- const existing = graph.getEdgeAttributes(id);
1189
- const updated = {
1190
- ...existing,
1191
- provenance: Provenance3.FRONTIER,
1192
- lastObserved: ts,
1193
- callCount: (existing.callCount ?? 0) + 1
1194
- };
1195
- graph.replaceEdgeAttributes(id, updated);
1196
- return;
1197
- }
1198
- const edge = {
1199
- id,
1200
- source,
1201
- target,
1202
- type,
1203
- provenance: Provenance3.FRONTIER,
1204
- confidence: 1,
1205
- lastObserved: ts,
1206
- callCount: 1
1207
- };
1208
- graph.addEdgeWithKey(id, source, target, edge);
1209
- }
1210
1187
  function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1211
1188
  if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
1212
1189
  const id = makeObservedEdgeId(type, source, target);
@@ -1214,35 +1191,37 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1214
1191
  const existing = graph.getEdgeAttributes(id);
1215
1192
  const newSpanCount = (existing.signal?.spanCount ?? existing.callCount ?? 0) + 1;
1216
1193
  const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0);
1194
+ const newSignal = {
1195
+ spanCount: newSpanCount,
1196
+ errorCount: newErrorCount,
1197
+ lastObservedAgeMs: 0
1198
+ };
1217
1199
  const updated = {
1218
1200
  ...existing,
1219
- provenance: Provenance3.OBSERVED,
1201
+ provenance: Provenance.OBSERVED,
1220
1202
  lastObserved: ts,
1221
1203
  callCount: newSpanCount,
1222
- signal: {
1223
- spanCount: newSpanCount,
1224
- errorCount: newErrorCount,
1225
- lastObservedAgeMs: 0
1226
- },
1227
- confidence: 1
1204
+ signal: newSignal,
1205
+ confidence: confidenceForObservedSignal(newSignal)
1228
1206
  };
1229
1207
  graph.replaceEdgeAttributes(id, updated);
1230
1208
  return { edge: updated, created: false };
1231
1209
  }
1210
+ const signal = {
1211
+ spanCount: 1,
1212
+ errorCount: isError ? 1 : 0,
1213
+ lastObservedAgeMs: 0
1214
+ };
1232
1215
  const edge = {
1233
1216
  id,
1234
1217
  source,
1235
1218
  target,
1236
1219
  type,
1237
- provenance: Provenance3.OBSERVED,
1238
- confidence: 1,
1220
+ provenance: Provenance.OBSERVED,
1221
+ confidence: confidenceForObservedSignal(signal),
1239
1222
  lastObserved: ts,
1240
1223
  callCount: 1,
1241
- signal: {
1242
- spanCount: 1,
1243
- errorCount: isError ? 1 : 0,
1244
- lastObservedAgeMs: 0
1245
- }
1224
+ signal
1246
1225
  };
1247
1226
  graph.addEdgeWithKey(id, source, target, edge);
1248
1227
  return { edge, created: true };
@@ -1257,7 +1236,7 @@ function stitchTrace(graph, sourceServiceId, ts) {
1257
1236
  const outbound = graph.outboundEdges(nodeId);
1258
1237
  for (const edgeId of outbound) {
1259
1238
  const edge = graph.getEdgeAttributes(edgeId);
1260
- if (edge.provenance !== Provenance3.EXTRACTED) continue;
1239
+ if (edge.provenance !== Provenance.EXTRACTED) continue;
1261
1240
  if (graph.hasEdge(observedEdgeId(edge.source, edge.target, edge.type))) continue;
1262
1241
  upsertInferredEdge(graph, edge.type, edge.source, edge.target, ts);
1263
1242
  if (!visited.has(edge.target)) {
@@ -1280,7 +1259,7 @@ function upsertInferredEdge(graph, type, source, target, ts) {
1280
1259
  source,
1281
1260
  target,
1282
1261
  type,
1283
- provenance: Provenance3.INFERRED,
1262
+ provenance: Provenance.INFERRED,
1284
1263
  confidence: INFERRED_CONFIDENCE,
1285
1264
  lastObserved: ts
1286
1265
  };
@@ -1290,18 +1269,28 @@ async function appendErrorEvent(ctx, ev) {
1290
1269
  await fs3.mkdir(path3.dirname(ctx.errorsPath), { recursive: true });
1291
1270
  await fs3.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
1292
1271
  }
1272
+ function sanitizeAttributes(attrs) {
1273
+ const out = {};
1274
+ for (const [k, v] of Object.entries(attrs)) {
1275
+ if (typeof v === "bigint") out[k] = v.toString();
1276
+ else out[k] = v;
1277
+ }
1278
+ return out;
1279
+ }
1293
1280
  function buildErrorEventForReceiver(span) {
1294
1281
  if (span.statusCode !== 2) return null;
1295
1282
  const ts = span.startTimeIso ?? (/* @__PURE__ */ new Date()).toISOString();
1283
+ const attrs = sanitizeAttributes(span.attributes);
1296
1284
  return {
1297
1285
  id: `${span.traceId}:${span.spanId}`,
1298
1286
  timestamp: ts,
1299
1287
  service: span.service,
1300
1288
  traceId: span.traceId,
1301
1289
  spanId: span.spanId,
1302
- errorMessage: span.exception?.message ?? span.errorMessage ?? span.name ?? "unknown error",
1290
+ errorMessage: span.exception?.message ?? span.name ?? "unknown error",
1303
1291
  ...span.exception?.type ? { exceptionType: span.exception.type } : {},
1304
1292
  ...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
1293
+ ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
1305
1294
  affectedNode: serviceId(span.service)
1306
1295
  };
1307
1296
  }
@@ -1352,11 +1341,16 @@ async function handleSpan(ctx, span) {
1352
1341
  affectedNode = targetId;
1353
1342
  resolvedViaAddress = true;
1354
1343
  } else if (!targetId) {
1355
- const frontierId2 = ensureFrontierNode(ctx.graph, host, ts);
1356
- if (ctx.graph.hasNode(sourceId)) {
1357
- upsertFrontierEdge(ctx.graph, EdgeType2.CALLS, sourceId, frontierId2, ts);
1358
- }
1359
- affectedNode = frontierId2;
1344
+ const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
1345
+ upsertObservedEdge(
1346
+ ctx.graph,
1347
+ EdgeType2.CALLS,
1348
+ sourceId,
1349
+ frontierNodeId,
1350
+ ts,
1351
+ isError
1352
+ );
1353
+ affectedNode = frontierNodeId;
1360
1354
  resolvedViaAddress = true;
1361
1355
  }
1362
1356
  }
@@ -1378,15 +1372,17 @@ async function handleSpan(ctx, span) {
1378
1372
  if (span.statusCode === 2) {
1379
1373
  stitchTrace(ctx.graph, sourceId, ts);
1380
1374
  if (ctx.writeErrorEventInline !== false) {
1375
+ const attrs = sanitizeAttributes(span.attributes);
1381
1376
  const ev = {
1382
1377
  id: `${span.traceId}:${span.spanId}`,
1383
1378
  timestamp: ts,
1384
1379
  service: span.service,
1385
1380
  traceId: span.traceId,
1386
1381
  spanId: span.spanId,
1387
- errorMessage: span.exception?.message ?? span.errorMessage ?? span.name ?? "unknown error",
1382
+ errorMessage: span.exception?.message ?? span.name ?? "unknown error",
1388
1383
  ...span.exception?.type ? { exceptionType: span.exception.type } : {},
1389
1384
  ...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
1385
+ ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
1390
1386
  affectedNode
1391
1387
  };
1392
1388
  await appendErrorEvent(ctx, ev);
@@ -1442,8 +1438,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId3) {
1442
1438
  }
1443
1439
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
1444
1440
  graph.dropEdge(oldEdgeId);
1445
- const promotedProvenance = edge.provenance === Provenance3.FRONTIER ? Provenance3.OBSERVED : edge.provenance;
1446
- const newId = promotedProvenance === Provenance3.OBSERVED ? observedEdgeId(newSource, newTarget, edge.type) : promotedProvenance === Provenance3.INFERRED ? inferredEdgeId(newSource, newTarget, edge.type) : promotedProvenance === Provenance3.EXTRACTED ? extractedEdgeId(newSource, newTarget, edge.type) : frontierEdgeId(newSource, newTarget, edge.type);
1441
+ const newId = edge.provenance === Provenance.OBSERVED ? observedEdgeId(newSource, newTarget, edge.type) : edge.provenance === Provenance.INFERRED ? inferredEdgeId(newSource, newTarget, edge.type) : extractedEdgeId(newSource, newTarget, edge.type);
1447
1442
  if (graph.hasEdge(newId)) {
1448
1443
  const existing = graph.getEdgeAttributes(newId);
1449
1444
  const merged = {
@@ -1458,8 +1453,7 @@ function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
1458
1453
  ...edge,
1459
1454
  id: newId,
1460
1455
  source: newSource,
1461
- target: newTarget,
1462
- provenance: promotedProvenance
1456
+ target: newTarget
1463
1457
  };
1464
1458
  graph.addEdgeWithKey(newId, newSource, newTarget, rebuilt);
1465
1459
  }
@@ -1478,12 +1472,12 @@ async function markStaleEdges(graph, options = {}) {
1478
1472
  const project = options.project ?? DEFAULT_PROJECT;
1479
1473
  graph.forEachEdge((id, attrs) => {
1480
1474
  const e = attrs;
1481
- if (e.provenance !== Provenance3.OBSERVED) return;
1475
+ if (e.provenance !== Provenance.OBSERVED) return;
1482
1476
  if (!e.lastObserved) return;
1483
1477
  const threshold = thresholdForEdgeType(e.type, thresholds);
1484
1478
  const age = now - new Date(e.lastObserved).getTime();
1485
1479
  if (age > threshold) {
1486
- const updated = { ...e, provenance: Provenance3.STALE, confidence: 0.3 };
1480
+ const updated = { ...e, provenance: Provenance.STALE, confidence: 0.3 };
1487
1481
  graph.replaceEdgeAttributes(id, updated);
1488
1482
  events.push({
1489
1483
  edgeId: id,
@@ -1500,8 +1494,8 @@ async function markStaleEdges(graph, options = {}) {
1500
1494
  project,
1501
1495
  payload: {
1502
1496
  edgeId: id,
1503
- from: Provenance3.OBSERVED,
1504
- to: Provenance3.STALE
1497
+ from: Provenance.OBSERVED,
1498
+ to: Provenance.STALE
1505
1499
  }
1506
1500
  });
1507
1501
  }
@@ -1593,6 +1587,27 @@ function formatExtractionBanner(count) {
1593
1587
  if (count === 1) return `[neat] 1 file skipped due to parse errors`;
1594
1588
  return `[neat] ${count} files skipped due to parse errors`;
1595
1589
  }
1590
+ var droppedSink = [];
1591
+ function noteExtractedDropped(edge) {
1592
+ droppedSink.push(edge);
1593
+ }
1594
+ function drainDroppedExtracted() {
1595
+ return droppedSink.splice(0, droppedSink.length);
1596
+ }
1597
+ function isRejectedLogEnabled() {
1598
+ const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG;
1599
+ return raw === "1" || raw === "true";
1600
+ }
1601
+ async function writeRejectedExtracted(drops, rejectedPath) {
1602
+ if (drops.length === 0) return;
1603
+ await fs4.mkdir(path4.dirname(rejectedPath), { recursive: true });
1604
+ const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
1605
+ await fs4.appendFile(rejectedPath, lines, "utf8");
1606
+ }
1607
+ function formatPrecisionFloorBanner(count) {
1608
+ if (count === 1) return `[neat] 1 extracted edge dropped below precision floor`;
1609
+ return `[neat] ${count} extracted edges dropped below precision floor`;
1610
+ }
1596
1611
 
1597
1612
  // src/extract/services.ts
1598
1613
  import { promises as fs8 } from "fs";
@@ -2223,7 +2238,13 @@ async function addServiceAliases(graph, scanPath, services) {
2223
2238
 
2224
2239
  // src/extract/databases/index.ts
2225
2240
  import path17 from "path";
2226
- import { EdgeType as EdgeType3, NodeType as NodeType6, Provenance as Provenance4, databaseId as databaseId2 } from "@neat.is/types";
2241
+ import {
2242
+ EdgeType as EdgeType3,
2243
+ NodeType as NodeType6,
2244
+ Provenance as Provenance2,
2245
+ databaseId as databaseId2,
2246
+ confidenceForExtracted
2247
+ } from "@neat.is/types";
2227
2248
 
2228
2249
  // src/extract/databases/db-config-yaml.ts
2229
2250
  import path10 from "path";
@@ -2797,7 +2818,8 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
2797
2818
  source: service.node.id,
2798
2819
  target: dbNode.id,
2799
2820
  type: EdgeType3.CONNECTS_TO,
2800
- provenance: Provenance4.EXTRACTED,
2821
+ provenance: Provenance2.EXTRACTED,
2822
+ confidence: confidenceForExtracted("structural"),
2801
2823
  // ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
2802
2824
  // Ghost-edge cleanup keys retirement on this; the conditional
2803
2825
  // sourceFile spread that used to live here was a v0.1.x leftover.
@@ -2830,7 +2852,13 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
2830
2852
  // src/extract/configs.ts
2831
2853
  import { promises as fs12 } from "fs";
2832
2854
  import path18 from "path";
2833
- import { EdgeType as EdgeType4, NodeType as NodeType7, Provenance as Provenance5, configId } from "@neat.is/types";
2855
+ import {
2856
+ EdgeType as EdgeType4,
2857
+ NodeType as NodeType7,
2858
+ Provenance as Provenance3,
2859
+ configId,
2860
+ confidenceForExtracted as confidenceForExtracted2
2861
+ } from "@neat.is/types";
2834
2862
  async function walkConfigFiles(dir) {
2835
2863
  const out = [];
2836
2864
  async function walk(current) {
@@ -2870,7 +2898,8 @@ async function addConfigNodes(graph, services, scanPath) {
2870
2898
  source: service.node.id,
2871
2899
  target: node.id,
2872
2900
  type: EdgeType4.CONFIGURED_BY,
2873
- provenance: Provenance5.EXTRACTED,
2901
+ provenance: Provenance3.EXTRACTED,
2902
+ confidence: confidenceForExtracted2("structural"),
2874
2903
  evidence: { file: relPath.split(path18.sep).join("/") }
2875
2904
  };
2876
2905
  if (!graph.hasEdge(edge.id)) {
@@ -2883,14 +2912,25 @@ async function addConfigNodes(graph, services, scanPath) {
2883
2912
  }
2884
2913
 
2885
2914
  // src/extract/calls/index.ts
2886
- import { EdgeType as EdgeType6, NodeType as NodeType8, Provenance as Provenance7 } from "@neat.is/types";
2915
+ import {
2916
+ EdgeType as EdgeType6,
2917
+ NodeType as NodeType8,
2918
+ Provenance as Provenance5,
2919
+ confidenceForExtracted as confidenceForExtracted4,
2920
+ passesExtractedFloor as passesExtractedFloor2
2921
+ } from "@neat.is/types";
2887
2922
 
2888
2923
  // src/extract/calls/http.ts
2889
2924
  import path20 from "path";
2890
2925
  import Parser from "tree-sitter";
2891
2926
  import JavaScript from "tree-sitter-javascript";
2892
2927
  import Python from "tree-sitter-python";
2893
- import { EdgeType as EdgeType5, Provenance as Provenance6 } from "@neat.is/types";
2928
+ import {
2929
+ EdgeType as EdgeType5,
2930
+ Provenance as Provenance4,
2931
+ confidenceForExtracted as confidenceForExtracted3,
2932
+ passesExtractedFloor
2933
+ } from "@neat.is/types";
2894
2934
 
2895
2935
  // src/extract/calls/shared.ts
2896
2936
  import { promises as fs13 } from "fs";
@@ -3022,17 +3062,32 @@ async function addHttpCallEdges(graph, services) {
3022
3062
  for (const [targetId, evidenceFile] of seenTargets) {
3023
3063
  const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
3024
3064
  const line = lineOf(fileContent, `//${evidenceFile.host}`);
3065
+ const confidence = confidenceForExtracted3("hostname-shape-match");
3066
+ const ev = {
3067
+ file: path20.relative(service.dir, evidenceFile.file),
3068
+ line,
3069
+ snippet: snippet(fileContent, line)
3070
+ };
3071
+ const edgeId = extractedEdgeId2(service.node.id, targetId, EdgeType5.CALLS);
3072
+ if (!passesExtractedFloor(confidence)) {
3073
+ noteExtractedDropped({
3074
+ source: service.node.id,
3075
+ target: targetId,
3076
+ type: EdgeType5.CALLS,
3077
+ confidence,
3078
+ confidenceKind: "hostname-shape-match",
3079
+ evidence: ev
3080
+ });
3081
+ continue;
3082
+ }
3025
3083
  const edge = {
3026
- id: extractedEdgeId2(service.node.id, targetId, EdgeType5.CALLS),
3084
+ id: edgeId,
3027
3085
  source: service.node.id,
3028
3086
  target: targetId,
3029
3087
  type: EdgeType5.CALLS,
3030
- provenance: Provenance6.EXTRACTED,
3031
- evidence: {
3032
- file: path20.relative(service.dir, evidenceFile.file),
3033
- line,
3034
- snippet: snippet(fileContent, line)
3035
- }
3088
+ provenance: Provenance4.EXTRACTED,
3089
+ confidence,
3090
+ evidence: ev
3036
3091
  };
3037
3092
  if (!graph.hasEdge(edge.id)) {
3038
3093
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3070,6 +3125,10 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3070
3125
  name: topic,
3071
3126
  kind: "kafka-topic",
3072
3127
  edgeType,
3128
+ // `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})` —
3129
+ // framework-aware (kafkajs / node-rdkafka shape). Verified-call-site
3130
+ // tier (ADR-066).
3131
+ confidenceKind: "verified-call-site",
3073
3132
  evidence: {
3074
3133
  file: path21.relative(serviceDir, file.path),
3075
3134
  line,
@@ -3101,6 +3160,10 @@ function redisEndpointsFromFile(file, serviceDir) {
3101
3160
  name: host,
3102
3161
  kind: "redis",
3103
3162
  edgeType: "CALLS",
3163
+ // `redis://host` URL literal — the scheme is structural support, but no
3164
+ // call expression is verified to wire it through. URL-with-structural-
3165
+ // support tier (ADR-066).
3166
+ confidenceKind: "url-with-structural-support",
3104
3167
  evidence: {
3105
3168
  file: path22.relative(serviceDir, file.path),
3106
3169
  line,
@@ -3141,6 +3204,10 @@ function awsEndpointsFromFile(file, serviceDir) {
3141
3204
  name,
3142
3205
  kind,
3143
3206
  edgeType: "CALLS",
3207
+ // SDK marker (S3Client, GetCommand, etc.) plus a Bucket/TableName
3208
+ // literal — framework-aware recognizer, verified-call-site tier
3209
+ // (ADR-066).
3210
+ confidenceKind: "verified-call-site",
3144
3211
  evidence: {
3145
3212
  file: path23.relative(serviceDir, file.path),
3146
3213
  line,
@@ -3219,6 +3286,10 @@ function grpcEndpointsFromFile(file, serviceDir) {
3219
3286
  name,
3220
3287
  kind,
3221
3288
  edgeType: "CALLS",
3289
+ // `new <Name>Client(...)` with @aws-sdk/* or @grpc/grpc-js import
3290
+ // context — import-aware classification per #238. Verified-call-site
3291
+ // tier (ADR-066).
3292
+ confidenceKind: "verified-call-site",
3222
3293
  evidence: {
3223
3294
  file: path24.relative(serviceDir, file.path),
3224
3295
  line,
@@ -3279,13 +3350,26 @@ async function addExternalEndpointEdges(graph, services) {
3279
3350
  const edgeId = extractedEdgeId2(service.node.id, ep.infraId, edgeType);
3280
3351
  if (seenEdges.has(edgeId)) continue;
3281
3352
  seenEdges.add(edgeId);
3353
+ const confidence = confidenceForExtracted4(ep.confidenceKind);
3354
+ if (!passesExtractedFloor2(confidence)) {
3355
+ noteExtractedDropped({
3356
+ source: service.node.id,
3357
+ target: ep.infraId,
3358
+ type: edgeType,
3359
+ confidence,
3360
+ confidenceKind: ep.confidenceKind,
3361
+ evidence: ep.evidence
3362
+ });
3363
+ continue;
3364
+ }
3282
3365
  if (!graph.hasEdge(edgeId)) {
3283
3366
  const edge = {
3284
3367
  id: edgeId,
3285
3368
  source: service.node.id,
3286
3369
  target: ep.infraId,
3287
3370
  type: edgeType,
3288
- provenance: Provenance7.EXTRACTED,
3371
+ provenance: Provenance5.EXTRACTED,
3372
+ confidence,
3289
3373
  evidence: ep.evidence
3290
3374
  };
3291
3375
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3306,7 +3390,7 @@ async function addCallEdges(graph, services) {
3306
3390
 
3307
3391
  // src/extract/infra/docker-compose.ts
3308
3392
  import path25 from "path";
3309
- import { EdgeType as EdgeType7, Provenance as Provenance8 } from "@neat.is/types";
3393
+ import { EdgeType as EdgeType7, Provenance as Provenance6, confidenceForExtracted as confidenceForExtracted5 } from "@neat.is/types";
3310
3394
 
3311
3395
  // src/extract/infra/shared.ts
3312
3396
  import { NodeType as NodeType9, infraId as infraId5 } from "@neat.is/types";
@@ -3399,7 +3483,8 @@ async function addComposeInfra(graph, scanPath, services) {
3399
3483
  source: sourceId,
3400
3484
  target: targetId,
3401
3485
  type: EdgeType7.DEPENDS_ON,
3402
- provenance: Provenance8.EXTRACTED,
3486
+ provenance: Provenance6.EXTRACTED,
3487
+ confidence: confidenceForExtracted5("structural"),
3403
3488
  evidence: { file: evidenceFile }
3404
3489
  };
3405
3490
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3412,7 +3497,7 @@ async function addComposeInfra(graph, scanPath, services) {
3412
3497
  // src/extract/infra/dockerfile.ts
3413
3498
  import path26 from "path";
3414
3499
  import { promises as fs14 } from "fs";
3415
- import { EdgeType as EdgeType8, Provenance as Provenance9 } from "@neat.is/types";
3500
+ import { EdgeType as EdgeType8, Provenance as Provenance7, confidenceForExtracted as confidenceForExtracted6 } from "@neat.is/types";
3416
3501
  function runtimeImage(content) {
3417
3502
  const lines = content.split("\n");
3418
3503
  let last = null;
@@ -3458,7 +3543,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3458
3543
  source: service.node.id,
3459
3544
  target: node.id,
3460
3545
  type: EdgeType8.RUNS_ON,
3461
- provenance: Provenance9.EXTRACTED,
3546
+ provenance: Provenance7.EXTRACTED,
3547
+ confidence: confidenceForExtracted6("structural"),
3462
3548
  evidence: {
3463
3549
  file: path26.relative(scanPath, dockerfilePath).split(path26.sep).join("/")
3464
3550
  }
@@ -3573,16 +3659,19 @@ async function addInfra(graph, scanPath, services) {
3573
3659
  };
3574
3660
  }
3575
3661
 
3662
+ // src/extract/index.ts
3663
+ import path30 from "path";
3664
+
3576
3665
  // src/extract/retire.ts
3577
3666
  import { existsSync } from "fs";
3578
3667
  import path29 from "path";
3579
- import { Provenance as Provenance10 } from "@neat.is/types";
3668
+ import { Provenance as Provenance8 } from "@neat.is/types";
3580
3669
  function retireEdgesByFile(graph, file) {
3581
3670
  const normalized = file.split("\\").join("/");
3582
3671
  const toDrop = [];
3583
3672
  graph.forEachEdge((id, attrs) => {
3584
3673
  const edge = attrs;
3585
- if (edge.provenance !== Provenance10.EXTRACTED) return;
3674
+ if (edge.provenance !== Provenance8.EXTRACTED) return;
3586
3675
  if (!edge.evidence?.file) return;
3587
3676
  if (edge.evidence.file === normalized) toDrop.push(id);
3588
3677
  });
@@ -3594,7 +3683,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
3594
3683
  const bases = [scanPath, ...serviceDirs];
3595
3684
  graph.forEachEdge((id, attrs) => {
3596
3685
  const edge = attrs;
3597
- if (edge.provenance !== Provenance10.EXTRACTED) return;
3686
+ if (edge.provenance !== Provenance8.EXTRACTED) return;
3598
3687
  const evidenceFile = edge.evidence?.file;
3599
3688
  if (!evidenceFile) return;
3600
3689
  if (path29.isAbsolute(evidenceFile)) {
@@ -3636,13 +3725,26 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
3636
3725
  );
3637
3726
  }
3638
3727
  }
3728
+ const droppedEntries = drainDroppedExtracted();
3729
+ if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
3730
+ const rejectedPath = path30.join(path30.dirname(opts.errorsPath), "rejected.ndjson");
3731
+ try {
3732
+ await writeRejectedExtracted(droppedEntries, rejectedPath);
3733
+ } catch (err) {
3734
+ console.warn(
3735
+ `[neat] failed to write rejected extracted edges to ${rejectedPath}: ${err.message}`
3736
+ );
3737
+ }
3738
+ }
3639
3739
  const result = {
3640
3740
  nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
3641
3741
  edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
3642
3742
  frontiersPromoted,
3643
3743
  extractionErrors: errorEntries.length,
3644
3744
  errorEntries,
3645
- ghostsRetired
3745
+ ghostsRetired,
3746
+ extractedDropped: droppedEntries.length,
3747
+ droppedEntries
3646
3748
  };
3647
3749
  emitNeatEvent({
3648
3750
  type: "extraction-complete",
@@ -3659,8 +3761,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
3659
3761
 
3660
3762
  // src/persist.ts
3661
3763
  import { promises as fs17 } from "fs";
3662
- import path30 from "path";
3663
- var SCHEMA_VERSION = 2;
3764
+ import path31 from "path";
3765
+ import { Provenance as Provenance9, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
3766
+ var SCHEMA_VERSION = 3;
3664
3767
  function migrateV1ToV2(payload) {
3665
3768
  const nodes = payload.graph.nodes;
3666
3769
  if (Array.isArray(nodes)) {
@@ -3672,8 +3775,27 @@ function migrateV1ToV2(payload) {
3672
3775
  }
3673
3776
  return { ...payload, schemaVersion: 2 };
3674
3777
  }
3778
+ function migrateV2ToV3(payload) {
3779
+ const edges = payload.graph.edges;
3780
+ if (Array.isArray(edges)) {
3781
+ for (const edge of edges) {
3782
+ const attrs = edge.attributes;
3783
+ if (!attrs || attrs.provenance !== "FRONTIER") continue;
3784
+ attrs.provenance = Provenance9.OBSERVED;
3785
+ const type = typeof attrs.type === "string" ? attrs.type : void 0;
3786
+ const source = typeof attrs.source === "string" ? attrs.source : void 0;
3787
+ const target = typeof attrs.target === "string" ? attrs.target : void 0;
3788
+ if (type && source && target) {
3789
+ const newId = observedEdgeId2(source, target, type);
3790
+ attrs.id = newId;
3791
+ if (edge.key) edge.key = newId;
3792
+ }
3793
+ }
3794
+ }
3795
+ return { ...payload, schemaVersion: 3 };
3796
+ }
3675
3797
  async function ensureDir(filePath) {
3676
- await fs17.mkdir(path30.dirname(filePath), { recursive: true });
3798
+ await fs17.mkdir(path31.dirname(filePath), { recursive: true });
3677
3799
  }
3678
3800
  async function saveGraphToDisk(graph, outPath) {
3679
3801
  await ensureDir(outPath);
@@ -3698,6 +3820,9 @@ async function loadGraphFromDisk(graph, outPath) {
3698
3820
  if (payload.schemaVersion === 1) {
3699
3821
  payload = migrateV1ToV2(payload);
3700
3822
  }
3823
+ if (payload.schemaVersion === 2) {
3824
+ payload = migrateV2ToV3(payload);
3825
+ }
3701
3826
  if (payload.schemaVersion !== SCHEMA_VERSION) {
3702
3827
  throw new Error(
3703
3828
  `persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`
@@ -3817,23 +3942,23 @@ function canonicalJson(value) {
3817
3942
  }
3818
3943
 
3819
3944
  // src/projects.ts
3820
- import path31 from "path";
3945
+ import path32 from "path";
3821
3946
  function pathsForProject(project, baseDir) {
3822
3947
  if (project === DEFAULT_PROJECT) {
3823
3948
  return {
3824
- snapshotPath: path31.join(baseDir, "graph.json"),
3825
- errorsPath: path31.join(baseDir, "errors.ndjson"),
3826
- staleEventsPath: path31.join(baseDir, "stale-events.ndjson"),
3827
- embeddingsCachePath: path31.join(baseDir, "embeddings.json"),
3828
- policyViolationsPath: path31.join(baseDir, "policy-violations.ndjson")
3949
+ snapshotPath: path32.join(baseDir, "graph.json"),
3950
+ errorsPath: path32.join(baseDir, "errors.ndjson"),
3951
+ staleEventsPath: path32.join(baseDir, "stale-events.ndjson"),
3952
+ embeddingsCachePath: path32.join(baseDir, "embeddings.json"),
3953
+ policyViolationsPath: path32.join(baseDir, "policy-violations.ndjson")
3829
3954
  };
3830
3955
  }
3831
3956
  return {
3832
- snapshotPath: path31.join(baseDir, `${project}.json`),
3833
- errorsPath: path31.join(baseDir, `errors.${project}.ndjson`),
3834
- staleEventsPath: path31.join(baseDir, `stale-events.${project}.ndjson`),
3835
- embeddingsCachePath: path31.join(baseDir, `embeddings.${project}.json`),
3836
- policyViolationsPath: path31.join(baseDir, `policy-violations.${project}.ndjson`)
3957
+ snapshotPath: path32.join(baseDir, `${project}.json`),
3958
+ errorsPath: path32.join(baseDir, `errors.${project}.ndjson`),
3959
+ staleEventsPath: path32.join(baseDir, `stale-events.${project}.ndjson`),
3960
+ embeddingsCachePath: path32.join(baseDir, `embeddings.${project}.json`),
3961
+ policyViolationsPath: path32.join(baseDir, `policy-violations.${project}.ndjson`)
3837
3962
  };
3838
3963
  }
3839
3964
  var Projects = class {
@@ -3874,7 +3999,7 @@ function parseExtraProjects(raw) {
3874
3999
  // src/registry.ts
3875
4000
  import { promises as fs19 } from "fs";
3876
4001
  import os2 from "os";
3877
- import path32 from "path";
4002
+ import path33 from "path";
3878
4003
  import {
3879
4004
  RegistryFileSchema
3880
4005
  } from "@neat.is/types";
@@ -3882,17 +4007,17 @@ var LOCK_TIMEOUT_MS = 5e3;
3882
4007
  var LOCK_RETRY_MS = 50;
3883
4008
  function neatHome() {
3884
4009
  const override = process.env.NEAT_HOME;
3885
- if (override && override.length > 0) return path32.resolve(override);
3886
- return path32.join(os2.homedir(), ".neat");
4010
+ if (override && override.length > 0) return path33.resolve(override);
4011
+ return path33.join(os2.homedir(), ".neat");
3887
4012
  }
3888
4013
  function registryPath() {
3889
- return path32.join(neatHome(), "projects.json");
4014
+ return path33.join(neatHome(), "projects.json");
3890
4015
  }
3891
4016
  function registryLockPath() {
3892
- return path32.join(neatHome(), "projects.json.lock");
4017
+ return path33.join(neatHome(), "projects.json.lock");
3893
4018
  }
3894
4019
  async function normalizeProjectPath(input) {
3895
- const resolved = path32.resolve(input);
4020
+ const resolved = path33.resolve(input);
3896
4021
  try {
3897
4022
  return await fs19.realpath(resolved);
3898
4023
  } catch {
@@ -3900,7 +4025,7 @@ async function normalizeProjectPath(input) {
3900
4025
  }
3901
4026
  }
3902
4027
  async function writeAtomically(target, contents) {
3903
- await fs19.mkdir(path32.dirname(target), { recursive: true });
4028
+ await fs19.mkdir(path33.dirname(target), { recursive: true });
3904
4029
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
3905
4030
  const fd = await fs19.open(tmp, "w");
3906
4031
  try {
@@ -3913,7 +4038,7 @@ async function writeAtomically(target, contents) {
3913
4038
  }
3914
4039
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
3915
4040
  const deadline = Date.now() + timeoutMs;
3916
- await fs19.mkdir(path32.dirname(lockPath), { recursive: true });
4041
+ await fs19.mkdir(path33.dirname(lockPath), { recursive: true });
3917
4042
  while (true) {
3918
4043
  try {
3919
4044
  const fd = await fs19.open(lockPath, "wx");
@@ -4051,7 +4176,7 @@ import {
4051
4176
  EdgeType as EdgeType9,
4052
4177
  NodeType as NodeType10,
4053
4178
  parseEdgeId,
4054
- Provenance as Provenance11
4179
+ Provenance as Provenance10
4055
4180
  } from "@neat.is/types";
4056
4181
  function bucketKey(source, target, type) {
4057
4182
  return `${type}|${source}|${target}`;
@@ -4065,20 +4190,17 @@ function bucketEdges(graph) {
4065
4190
  const key = bucketKey(e.source, e.target, e.type);
4066
4191
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
4067
4192
  switch (provenance) {
4068
- case Provenance11.EXTRACTED:
4193
+ case Provenance10.EXTRACTED:
4069
4194
  cur.extracted = e;
4070
4195
  break;
4071
- case Provenance11.OBSERVED:
4196
+ case Provenance10.OBSERVED:
4072
4197
  cur.observed = e;
4073
4198
  break;
4074
- case Provenance11.INFERRED:
4199
+ case Provenance10.INFERRED:
4075
4200
  cur.inferred = e;
4076
4201
  break;
4077
- case Provenance11.FRONTIER:
4078
- cur.frontier = e;
4079
- break;
4080
4202
  default:
4081
- if (e.provenance === Provenance11.STALE) cur.stale = e;
4203
+ if (e.provenance === Provenance10.STALE) cur.stale = e;
4082
4204
  }
4083
4205
  buckets.set(key, cur);
4084
4206
  });
@@ -4089,14 +4211,6 @@ function nodeIsFrontier(graph, nodeId) {
4089
4211
  const attrs = graph.getNodeAttributes(nodeId);
4090
4212
  return attrs.type === NodeType10.FrontierNode;
4091
4213
  }
4092
- function hasAnyObservedFromSource(graph, sourceId) {
4093
- if (!graph.hasNode(sourceId)) return false;
4094
- for (const edgeId of graph.outboundEdges(sourceId)) {
4095
- const e = graph.getEdgeAttributes(edgeId);
4096
- if (e.provenance === Provenance11.OBSERVED) return true;
4097
- }
4098
- return false;
4099
- }
4100
4214
  function clampConfidence(n) {
4101
4215
  if (!Number.isFinite(n)) return 0;
4102
4216
  return Math.max(0, Math.min(1, n));
@@ -4110,32 +4224,34 @@ function reasonForMissingExtracted(source, target, type) {
4110
4224
  var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
4111
4225
  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.";
4112
4226
  var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
4227
+ function gradedConfidence(edge) {
4228
+ if (typeof edge.confidence === "number") return clampConfidence(edge.confidence);
4229
+ return clampConfidence(confidenceForEdge(edge));
4230
+ }
4113
4231
  function detectMissingDivergences(graph, bucket) {
4114
4232
  const out = [];
4115
4233
  if (bucket.extracted && !bucket.observed) {
4116
4234
  if (!nodeIsFrontier(graph, bucket.target)) {
4117
- const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
4118
4235
  out.push({
4119
4236
  type: "missing-observed",
4120
4237
  source: bucket.source,
4121
4238
  target: bucket.target,
4122
4239
  edgeType: bucket.type,
4123
4240
  extracted: bucket.extracted,
4124
- confidence: sourceHasTraffic ? 1 : 0.5,
4241
+ confidence: gradedConfidence(bucket.extracted),
4125
4242
  reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
4126
4243
  recommendation: RECOMMENDATION_MISSING_OBSERVED
4127
4244
  });
4128
4245
  }
4129
4246
  }
4130
4247
  if (bucket.observed && !bucket.extracted) {
4131
- const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
4132
4248
  out.push({
4133
4249
  type: "missing-extracted",
4134
4250
  source: bucket.source,
4135
4251
  target: bucket.target,
4136
4252
  edgeType: bucket.type,
4137
4253
  observed: bucket.observed,
4138
- confidence: cascaded,
4254
+ confidence: gradedConfidence(bucket.observed),
4139
4255
  reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
4140
4256
  recommendation: RECOMMENDATION_MISSING_EXTRACTED
4141
4257
  });
@@ -4154,7 +4270,7 @@ function declaredHostFor(svc) {
4154
4270
  function hasExtractedConfiguredBy(graph, svcId) {
4155
4271
  for (const edgeId of graph.outboundEdges(svcId)) {
4156
4272
  const e = graph.getEdgeAttributes(edgeId);
4157
- if (e.type === EdgeType9.CONFIGURED_BY && e.provenance === Provenance11.EXTRACTED) {
4273
+ if (e.type === EdgeType9.CONFIGURED_BY && e.provenance === Provenance10.EXTRACTED) {
4158
4274
  return true;
4159
4275
  }
4160
4276
  }
@@ -4168,7 +4284,7 @@ function detectHostMismatch(graph, svcId, svc) {
4168
4284
  for (const edgeId of graph.outboundEdges(svcId)) {
4169
4285
  const edge = graph.getEdgeAttributes(edgeId);
4170
4286
  if (edge.type !== EdgeType9.CONNECTS_TO) continue;
4171
- if (edge.provenance !== Provenance11.OBSERVED) continue;
4287
+ if (edge.provenance !== Provenance10.OBSERVED) continue;
4172
4288
  const target = graph.getNodeAttributes(edge.target);
4173
4289
  if (target.type !== NodeType10.DatabaseNode) continue;
4174
4290
  const observedHost = target.host?.trim();
@@ -4193,7 +4309,7 @@ function detectCompatDivergences(graph, svcId, svc) {
4193
4309
  for (const edgeId of graph.outboundEdges(svcId)) {
4194
4310
  const edge = graph.getEdgeAttributes(edgeId);
4195
4311
  if (edge.type !== EdgeType9.CONNECTS_TO) continue;
4196
- if (edge.provenance !== Provenance11.OBSERVED) continue;
4312
+ if (edge.provenance !== Provenance10.OBSERVED) continue;
4197
4313
  const target = graph.getNodeAttributes(edge.target);
4198
4314
  if (target.type !== NodeType10.DatabaseNode) continue;
4199
4315
  for (const pair of compatPairs()) {
@@ -4274,8 +4390,17 @@ function computeDivergences(graph, opts = {}) {
4274
4390
  const target = opts.node;
4275
4391
  filtered = filtered.filter((d) => involvesNode(d, target));
4276
4392
  }
4393
+ const TYPE_LEADERSHIP = {
4394
+ "missing-extracted": 0,
4395
+ "missing-observed": 1,
4396
+ "version-mismatch": 2,
4397
+ "host-mismatch": 3,
4398
+ "compat-violation": 4
4399
+ };
4277
4400
  filtered.sort((a, b) => {
4278
4401
  if (b.confidence !== a.confidence) return b.confidence - a.confidence;
4402
+ const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type];
4403
+ if (lead !== 0) return lead;
4279
4404
  if (a.type !== b.type) return a.type.localeCompare(b.type);
4280
4405
  if (a.source !== b.source) return a.source.localeCompare(b.source);
4281
4406
  return a.target.localeCompare(b.target);
@@ -4795,6 +4920,7 @@ export {
4795
4920
  readErrorEvents,
4796
4921
  isStrictExtractionEnabled,
4797
4922
  formatExtractionBanner,
4923
+ formatPrecisionFloorBanner,
4798
4924
  discoverServices,
4799
4925
  addServiceNodes,
4800
4926
  addServiceAliases,
@@ -4826,4 +4952,4 @@ export {
4826
4952
  removeProject,
4827
4953
  buildApi
4828
4954
  };
4829
- //# sourceMappingURL=chunk-VABXPLDT.js.map
4955
+ //# sourceMappingURL=chunk-BUB3ASD5.js.map