@neat.is/core 0.9.13 → 0.9.15-dev.20260902

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.
@@ -935,6 +935,7 @@ import {
935
935
  inferredEdgeId,
936
936
  infraId,
937
937
  observedEdgeId,
938
+ frontierEdgeId,
938
939
  serviceId,
939
940
  symbolId,
940
941
  websocketChannelId
@@ -5117,6 +5118,32 @@ function promoteFrontierNodes(graph, opts = {}) {
5117
5118
  }
5118
5119
  return promoted;
5119
5120
  }
5121
+ function stageFrontierEdge(graph, source, target, type) {
5122
+ if (!graph.hasNode(source) || !graph.hasNode(target)) return false;
5123
+ if (graph.hasEdge(observedEdgeId(source, target, type))) return false;
5124
+ const key = frontierEdgeId(source, target, type);
5125
+ if (graph.hasEdge(key)) return false;
5126
+ graph.addEdgeWithKey(key, source, target, {
5127
+ id: key,
5128
+ source,
5129
+ target,
5130
+ type,
5131
+ provenance: Provenance5.FRONTIER
5132
+ });
5133
+ return true;
5134
+ }
5135
+ function promoteFrontierEdges(graph) {
5136
+ const graduated = [];
5137
+ graph.forEachEdge((edgeId, attrs) => {
5138
+ const e = attrs;
5139
+ if (e.provenance !== Provenance5.FRONTIER) return;
5140
+ if (graph.hasEdge(observedEdgeId(e.source, e.target, e.type))) {
5141
+ graduated.push(edgeId);
5142
+ }
5143
+ });
5144
+ for (const edgeId of graduated) graph.dropEdge(edgeId);
5145
+ return graduated.length;
5146
+ }
5120
5147
  function rewireFrontierEdges(graph, frontierId2, serviceId17) {
5121
5148
  const inbound = [...graph.inboundEdges(frontierId2)];
5122
5149
  const outbound = [...graph.outboundEdges(frontierId2)];
@@ -5225,6 +5252,7 @@ function startStalenessLoop(graph, options = {}) {
5225
5252
  project: options.project
5226
5253
  });
5227
5254
  if (options.onPolicyTrigger) await options.onPolicyTrigger(graph);
5255
+ if (options.onReconcile) await options.onReconcile(graph);
5228
5256
  } catch (err) {
5229
5257
  console.error("staleness tick failed", err);
5230
5258
  }
@@ -5368,6 +5396,7 @@ import {
5368
5396
  RootCauseResultSchema,
5369
5397
  TransitiveDependenciesResultSchema,
5370
5398
  fileId as fileId4,
5399
+ frontierEdgeId as frontierEdgeId2,
5371
5400
  parseSymbolId
5372
5401
  } from "@neat.is/types";
5373
5402
  var ROOT_CAUSE_MAX_DEPTH = 5;
@@ -5398,13 +5427,19 @@ function resolveOwningService(graph, nodeId) {
5398
5427
  }
5399
5428
  return null;
5400
5429
  }
5430
+ function isFrontierEdge(e) {
5431
+ return e.provenance === Provenance6.FRONTIER;
5432
+ }
5433
+ function rankOf(p) {
5434
+ return p === Provenance6.FRONTIER ? -1 : PROV_RANK[p];
5435
+ }
5401
5436
  function bestEdgeBySource(graph, edgeIds) {
5402
5437
  const best = /* @__PURE__ */ new Map();
5403
5438
  for (const id of edgeIds) {
5404
5439
  const e = graph.getEdgeAttributes(id);
5405
- if (isFrontierNode(graph, e.source)) continue;
5440
+ if (isFrontierNode(graph, e.source) || isFrontierEdge(e)) continue;
5406
5441
  const cur = best.get(e.source);
5407
- if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {
5442
+ if (!cur || rankOf(e.provenance) > rankOf(cur.provenance)) {
5408
5443
  best.set(e.source, e);
5409
5444
  }
5410
5445
  }
@@ -5414,9 +5449,9 @@ function bestEdgeByTarget(graph, edgeIds) {
5414
5449
  const best = /* @__PURE__ */ new Map();
5415
5450
  for (const id of edgeIds) {
5416
5451
  const e = graph.getEdgeAttributes(id);
5417
- if (isFrontierNode(graph, e.target)) continue;
5452
+ if (isFrontierNode(graph, e.target) || isFrontierEdge(e)) continue;
5418
5453
  const cur = best.get(e.target);
5419
- if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {
5454
+ if (!cur || rankOf(e.provenance) > rankOf(cur.provenance)) {
5420
5455
  best.set(e.target, e);
5421
5456
  }
5422
5457
  }
@@ -5426,7 +5461,10 @@ var PROVENANCE_CEILING = {
5426
5461
  OBSERVED: 1,
5427
5462
  INFERRED: 0.7,
5428
5463
  EXTRACTED: 0.5,
5429
- STALE: 0.3
5464
+ STALE: 0.3,
5465
+ // A FRONTIER surface (ADR-226) is a proposal about a cause NEAT could not observe
5466
+ // — the least-trusted claim it makes, below STALE (which was at least once seen).
5467
+ FRONTIER: 0.2
5430
5468
  };
5431
5469
  function volumeWeight(spanCount) {
5432
5470
  if (!spanCount || spanCount <= 0) return 0.5;
@@ -5661,7 +5699,7 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
5661
5699
  });
5662
5700
  }
5663
5701
  function isFailingCallEdge(e) {
5664
- return e.type === EdgeType6.CALLS && (e.signal?.errorCount ?? 0) > 0;
5702
+ return e.type === EdgeType6.CALLS && !isFrontierEdge(e) && (e.signal?.errorCount ?? 0) > 0;
5665
5703
  }
5666
5704
  function callSourcesForService(graph, serviceId17) {
5667
5705
  const ids = [serviceId17];
@@ -5684,8 +5722,8 @@ function failingCallDominates(e, id, curEdge, curId) {
5684
5722
  const ec = e.signal?.errorCount ?? 0;
5685
5723
  const cc = curEdge.signal?.errorCount ?? 0;
5686
5724
  if (ec !== cc) return ec > cc;
5687
- if (PROV_RANK[e.provenance] !== PROV_RANK[curEdge.provenance]) {
5688
- return PROV_RANK[e.provenance] > PROV_RANK[curEdge.provenance];
5725
+ if (rankOf(e.provenance) !== rankOf(curEdge.provenance)) {
5726
+ return rankOf(e.provenance) > rankOf(curEdge.provenance);
5689
5727
  }
5690
5728
  return id < curId;
5691
5729
  }
@@ -5736,11 +5774,11 @@ function dominantStaleCall(graph, serviceId17, visited) {
5736
5774
  for (const edgeId of graph.outboundEdges(src)) {
5737
5775
  const e = graph.getEdgeAttributes(edgeId);
5738
5776
  if (e.type !== EdgeType6.CALLS) continue;
5739
- if (isFrontierNode(graph, e.target)) continue;
5777
+ if (isFrontierNode(graph, e.target) || isFrontierEdge(e)) continue;
5740
5778
  const owner = resolveOwningService(graph, e.target);
5741
5779
  if (!owner || visited.has(owner.id)) continue;
5742
5780
  const cur = bestByCallee.get(owner.id);
5743
- if (!cur || PROV_RANK[e.provenance] > PROV_RANK[cur.provenance]) {
5781
+ if (!cur || rankOf(e.provenance) > rankOf(cur.provenance)) {
5744
5782
  bestByCallee.set(owner.id, e);
5745
5783
  }
5746
5784
  }
@@ -6013,6 +6051,7 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
6013
6051
  for (const edgeId of graph.inboundEdges(n)) {
6014
6052
  const e = graph.getEdgeAttributes(edgeId);
6015
6053
  if (e.type === EdgeType6.CONTAINS) continue;
6054
+ if (isFrontierEdge(e)) continue;
6016
6055
  errorsFromCallers += e.signal?.errorCount ?? 0;
6017
6056
  inboundVolume += e.callCount ?? e.signal?.spanCount ?? 0;
6018
6057
  if (e.provenance === Provenance6.STALE) stale = true;
@@ -6026,6 +6065,7 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
6026
6065
  for (const edgeId of graph.outboundEdges(n)) {
6027
6066
  const e = graph.getEdgeAttributes(edgeId);
6028
6067
  if (e.type === EdgeType6.CONTAINS) continue;
6068
+ if (isFrontierEdge(e)) continue;
6029
6069
  outboundVolume += e.callCount ?? e.signal?.spanCount ?? 0;
6030
6070
  if (e.type === EdgeType6.CALLS) outboundErrors += e.signal?.errorCount ?? 0;
6031
6071
  if (e.provenance === Provenance6.STALE) stale = true;
@@ -6309,22 +6349,44 @@ function routeMatches(declared, incident) {
6309
6349
  const b = normalizeRoute(incident);
6310
6350
  return a === b || a.startsWith(b) || b.startsWith(a);
6311
6351
  }
6312
- function structuralUpstreamPointer(graph, boundaryNode, incidents) {
6313
- const route = boundaryIncidentRoute(boundaryNode, incidents);
6314
- const callees = declaredOutboundCallees(graph, boundaryNode);
6352
+ function firstDeclaredCallee(graph, node, route) {
6353
+ const callees = declaredOutboundCallees(graph, node);
6315
6354
  if (callees.length === 0) return null;
6316
6355
  const narrowed = route !== void 0 ? callees.filter((c) => c.route !== void 0 && routeMatches(c.route, route)) : callees;
6317
6356
  const chosen = narrowed.length === 1 ? narrowed[0] : callees.length === 1 ? callees[0] : null;
6318
- if (!chosen) return null;
6319
- let node = chosen.target;
6320
- const seen = /* @__PURE__ */ new Set([boundaryNode, node]);
6321
- for (let depth = 0; depth < ROOT_CAUSE_MAX_DEPTH; depth++) {
6322
- const next = declaredOutboundCallees(graph, node);
6323
- if (next.length !== 1 || seen.has(next[0].target)) break;
6324
- node = next[0].target;
6325
- seen.add(node);
6357
+ return chosen ? chosen.target : null;
6358
+ }
6359
+ function observedOutboundNextServices(graph, nodeId) {
6360
+ const targets = /* @__PURE__ */ new Set();
6361
+ for (const n of nodeScope(graph, nodeId)) {
6362
+ if (!graph.hasNode(n)) continue;
6363
+ for (const edgeId of graph.outboundEdges(n)) {
6364
+ const e = graph.getEdgeAttributes(edgeId);
6365
+ if (!DEP_EDGE_TYPES.has(e.type)) continue;
6366
+ if (e.provenance !== Provenance6.OBSERVED) continue;
6367
+ if (e.target !== nodeId) targets.add(e.target);
6368
+ }
6326
6369
  }
6327
- return route !== void 0 ? { node, route } : { node };
6370
+ return [...targets];
6371
+ }
6372
+ function resolveHangHop(graph, boundaryNode, incidents) {
6373
+ const route = boundaryIncidentRoute(boundaryNode, incidents);
6374
+ const own = firstDeclaredCallee(graph, boundaryNode, route);
6375
+ if (own) {
6376
+ return route !== void 0 ? { source: boundaryNode, target: own, route } : { source: boundaryNode, target: own };
6377
+ }
6378
+ const resolved = observedOutboundNextServices(graph, boundaryNode).map((n) => ({ source: n, target: firstDeclaredCallee(graph, n, route) })).filter((r) => r.target !== null);
6379
+ if (resolved.length === 1) {
6380
+ const hop = resolved[0];
6381
+ return route !== void 0 ? { ...hop, route } : hop;
6382
+ }
6383
+ return null;
6384
+ }
6385
+ function stagedHangProposal(graph, boundaryNode, incidents) {
6386
+ const hop = resolveHangHop(graph, boundaryNode, incidents);
6387
+ if (!hop) return null;
6388
+ if (!graph.hasEdge(frontierEdgeId2(hop.source, hop.target, EdgeType6.CALLS))) return null;
6389
+ return hop.route !== void 0 ? { node: hop.target, route: hop.route } : { node: hop.target };
6328
6390
  }
6329
6391
  function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
6330
6392
  const legacy = tagged.result;
@@ -6370,28 +6432,28 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
6370
6432
  provenance: Provenance6.OBSERVED
6371
6433
  });
6372
6434
  } else if (seedCtx && isBoundaryTimeoutSymptom(seedCtx)) {
6373
- const pointer = structuralUpstreamPointer(graph, seedNode, incidents);
6374
6435
  const seedName = displayNameOf(seedNode);
6375
- const routeNote = pointer?.route ? ` serving ${pointer.route}` : "";
6376
- if (pointer) {
6377
- const causeName = displayNameOf(pointer.node);
6378
- candidates.push({
6379
- node: pointer.node,
6380
- classification: "primary-failure",
6381
- reason: `${causeName} is the likely root cause (structural, INFERRED \u2014 not observed): the boundary ${seedName} only timed out waiting, and the actual culprit hung without exporting a span, so this is walked from the declared call graph${routeNote}, not from runtime signal. Restore instrumentation / inspect ${causeName} to confirm.`,
6382
- context: nodeContext(graph, pointer.node, incidents, now),
6383
- confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.EXTRACTED),
6384
- provenance: Provenance6.INFERRED
6385
- });
6386
- }
6436
+ const proposal = stagedHangProposal(graph, seedNode, incidents);
6437
+ const routeNote = proposal?.route ? ` serving ${proposal.route}` : "";
6387
6438
  candidates.push({
6388
6439
  node: seedNode,
6389
6440
  classification: "symptom-only",
6390
- reason: pointer ? `${seedName} timed out waiting on a downstream that hung and exported nothing \u2014 a boundary reporting an upstream fault, not the fault itself.` : `${seedName} timed out but nothing it can see downstream is erroring \u2014 the cause is downstream and unobserved (the culprit hung and exported no span). No single declared callee${routeNote} resolves, so no confident cause is named; inspect ${seedName}'s declared downstream.`,
6441
+ reason: proposal ? `${seedName} timed out waiting on a downstream that hung and exported no span \u2014 a boundary reporting an upstream fault, not the fault itself. The cause is unobservable; NEAT proposes the staged FRONTIER surface below, a hypothesis to confirm, not an observed cause.` : `${seedName} timed out but nothing it can see downstream is erroring \u2014 the cause is downstream and unobserved (the culprit hung and exported no span). No FRONTIER surface${routeNote} is staged, so no cause is proposed; inspect ${seedName}'s declared downstream and restore instrumentation.`,
6391
6442
  context: seedCtx,
6392
6443
  confidence: Math.min(legacy.confidence, 0.3),
6393
6444
  ...lastProv ? { provenance: lastProv } : {}
6394
6445
  });
6446
+ if (proposal) {
6447
+ const causeName = displayNameOf(proposal.node);
6448
+ candidates.push({
6449
+ node: proposal.node,
6450
+ classification: "primary-failure",
6451
+ reason: `${causeName} is the proposed hang cause (FRONTIER \u2014 a hypothesis, not observed): the boundary ${seedName} only timed out waiting and the culprit hung without exporting a span, so NEAT staged a FRONTIER surface on the unobservable hop${routeNote}. It graduates to OBSERVED when ${causeName} comes back; until then, inspect it / restore instrumentation to confirm, or it is culled.`,
6452
+ context: nodeContext(graph, proposal.node, incidents, now),
6453
+ confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.FRONTIER),
6454
+ provenance: Provenance6.FRONTIER
6455
+ });
6456
+ }
6395
6457
  } else if (staleChain) {
6396
6458
  const culprit = staleChain.culprit;
6397
6459
  const culpritName = displayNameOf(culprit);
@@ -6455,6 +6517,9 @@ function fixRecommendationForTop(top, seedNode, legacy) {
6455
6517
  return legacy.fixRecommendation;
6456
6518
  }
6457
6519
  const name = top.node.replace(/^service:/, "");
6520
+ if (top.classification === "symptom-only" && top.context.boundaryTimeout === true) {
6521
+ return `${name} timed out waiting on a downstream that hung and exported no span \u2014 the cause is unobservable from traces. Inspect the FRONTIER surface NEAT staged on the unobservable hop (see candidates); it graduates to OBSERVED when that service recovers. Restore instrumentation on that path to confirm, rather than treating ${name} as the fault.`;
6522
+ }
6458
6523
  if (top.provenance === Provenance6.STALE) {
6459
6524
  return `Live telemetry for this path has gone quiet; the last-observed topology traces the failure downstream to ${name}. Restore instrumentation (or re-run with live traces) to confirm, then inspect ${name}.`;
6460
6525
  }
@@ -22708,7 +22773,10 @@ export {
22708
22773
  attachGraphToEventBus,
22709
22774
  confidenceForEdge,
22710
22775
  getBlastRadius,
22776
+ nodeContext,
22777
+ isBoundaryTimeoutSymptom,
22711
22778
  getRootCause,
22779
+ resolveHangHop,
22712
22780
  evaluateAllPolicies,
22713
22781
  loadPolicyFile,
22714
22782
  PolicyViolationsLog,
@@ -22723,6 +22791,8 @@ export {
22723
22791
  makeErrorSpanWriter,
22724
22792
  handleSpan,
22725
22793
  promoteFrontierNodes,
22794
+ stageFrontierEdge,
22795
+ promoteFrontierEdges,
22726
22796
  makeSpanHandler,
22727
22797
  markStaleEdges,
22728
22798
  readStaleEvents,
@@ -22787,4 +22857,4 @@ export {
22787
22857
  deprovisionConnector,
22788
22858
  buildApi
22789
22859
  };
22790
- //# sourceMappingURL=chunk-RL6H3VVD.js.map
22860
+ //# sourceMappingURL=chunk-SSLPBQWY.js.map