@neat.is/core 0.8.1-dev.20260816 → 0.8.1

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/cli.cjs CHANGED
@@ -1472,7 +1472,11 @@ function legacyRootCause(graph, errorNodeId, errorEvent, incidents) {
1472
1472
  }
1473
1473
  var INCIDENT_ROOT_CAUSE_CONFIDENCE = 0.6;
1474
1474
  function incidentMatchesNode(ev, nodeId) {
1475
- return ev.affectedNode === nodeId || ev.service === nodeId.replace(/^service:/, "");
1475
+ if (ev.affectedNode === nodeId) return true;
1476
+ if (ev.service === nodeId.replace(/^service:/, "")) return true;
1477
+ const sym = (0, import_types.parseSymbolId)(ev.affectedNode);
1478
+ if (sym && (0, import_types.fileId)(sym.service, sym.relPath) === nodeId) return true;
1479
+ return false;
1476
1480
  }
1477
1481
  function localizeFromIncidents(nodeId, incidents, errorEvent) {
1478
1482
  const pool = incidents && incidents.length > 0 ? incidents : errorEvent ? [errorEvent] : [];
@@ -1490,8 +1494,8 @@ function localizeFromIncidents(nodeId, incidents, errorEvent) {
1490
1494
  const reasonParts = [`${latest.service}: ${latest.errorMessage}`];
1491
1495
  if (location) reasonParts.push(`surfaced at ${location}`);
1492
1496
  const rootCauseReason = `${reasonParts.join(" \u2014 ")}${tail}`;
1493
- const localizesToFile = latest.affectedNode !== nodeId && latest.affectedNode.startsWith("file:");
1494
- const fileNode = localizesToFile ? latest.affectedNode : void 0;
1497
+ const localizesFiner = latest.affectedNode !== nodeId && (latest.affectedNode.startsWith("file:") || latest.affectedNode.startsWith("symbol:"));
1498
+ const fileNode = localizesFiner ? latest.affectedNode : void 0;
1495
1499
  const fixRecommendation = location ? `Inspect ${location}${route ? ` handling ${route}` : ""}` : route ? `Inspect ${latest.service}'s handler for ${route}` : void 0;
1496
1500
  return {
1497
1501
  rootCauseNode: fileNode ?? nodeId,
@@ -1976,6 +1980,22 @@ function findLoadOrigin(graph, alertNodeId, incidents, now) {
1976
1980
  }
1977
1981
  return best ? { node: best.node, ctx: best.ctx } : null;
1978
1982
  }
1983
+ var LOAD_ORIGIN_CONFIDENCE_CEILING = 0.9;
1984
+ function loadOriginConfidence(originCtx, seedCtx) {
1985
+ const cleanSplit = seedCtx.errorsFromCallers > 0 && seedCtx.errorsEmittedHere === 0;
1986
+ const partialSplit = seedCtx.errorsFromCallers > seedCtx.errorsEmittedHere;
1987
+ let evidence = 0;
1988
+ evidence += cleanSplit ? 0.45 : partialSplit ? 0.2 : 0;
1989
+ evidence += seedCtx.stale ? 0.2 : 0;
1990
+ evidence += isSaturated(seedCtx) ? 0.15 : 0;
1991
+ evidence += (volumeWeight(originCtx.outboundVolume) - 0.5) * 0.4;
1992
+ const anchor = 0.45;
1993
+ const confidence = anchor + Math.min(1, evidence) * (LOAD_ORIGIN_CONFIDENCE_CEILING - anchor);
1994
+ return Math.max(0, Math.min(LOAD_ORIGIN_CONFIDENCE_CEILING, confidence));
1995
+ }
1996
+ function displayNameOf(nodeId) {
1997
+ return nodeId.replace(/^[a-z]+:/, "");
1998
+ }
1979
1999
  function getRootCause(graph, errorNodeId, errorEvent, incidents, opts) {
1980
2000
  const legacy = legacyRootCause(graph, errorNodeId, errorEvent, incidents);
1981
2001
  if (!legacy) return null;
@@ -1990,20 +2010,20 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
1990
2010
  const candidates = [];
1991
2011
  if (seedCtx && isVictimSeed(seedCtx)) {
1992
2012
  const origin = findLoadOrigin(graph, errorNodeId, incidents, now);
2013
+ const staleNote = seedCtx.stale ? "; it has gone STALE under load" : "";
2014
+ const satNote = isSaturated(seedCtx) ? `; its inbound p95 ${Math.round(seedCtx.latencyP95Ms)}ms is saturated` : "";
1993
2015
  if (origin) {
1994
- const path84 = findPath(graph, errorNodeId, origin.node, "up", ROOT_CAUSE_MAX_DEPTH);
1995
- const originConfidence = confidenceFromMix(path84?.edges ?? [], now);
2016
+ const originName = displayNameOf(origin.node);
2017
+ const seedName = displayNameOf(seedNode);
1996
2018
  candidates.push({
1997
2019
  node: origin.node,
1998
2020
  classification: "primary-failure",
1999
- reason: `Highest-volume upstream source (${origin.ctx.outboundVolume} observed outbound calls) driving a saturated/stale subgraph; the alerting path decays downstream into a starved victim rather than a fault at the callee.`,
2021
+ reason: `${originName} is the root cause: it overloads the failing subgraph, and the alerting node ${seedName} is a downstream symptom, not the fault \u2014 errors reach ${seedName} from its callers (${seedCtx.errorsFromCallers}), none originate there${staleNote}${satNote}. ${originName} is the upstream source driving that load (${origin.ctx.outboundVolume} observed outbound calls).`,
2000
2022
  context: origin.ctx,
2001
- confidence: Math.max(0.3, Math.min(0.8, originConfidence || 0.5)),
2023
+ confidence: loadOriginConfidence(origin.ctx, seedCtx),
2002
2024
  provenance: import_types.Provenance.OBSERVED
2003
2025
  });
2004
2026
  }
2005
- const staleNote = seedCtx.stale ? "; the node has gone STALE" : "";
2006
- const satNote = isSaturated(seedCtx) ? `; inbound p95 ${Math.round(seedCtx.latencyP95Ms)}ms is saturated` : "";
2007
2027
  candidates.push({
2008
2028
  node: seedNode,
2009
2029
  classification: "symptom-only",
@@ -2035,16 +2055,27 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
2035
2055
  edgeProvenances = [top.provenance ?? import_types.Provenance.OBSERVED];
2036
2056
  }
2037
2057
  }
2058
+ const fixRecommendation = fixRecommendationForTop(top, seedNode, legacy);
2038
2059
  return import_types.RootCauseResultSchema.parse({
2039
2060
  rootCauseNode: top.node,
2040
2061
  rootCauseReason: top.reason,
2041
2062
  traversalPath,
2042
2063
  edgeProvenances,
2043
2064
  confidence: top.confidence,
2044
- ...legacy.fixRecommendation ? { fixRecommendation: legacy.fixRecommendation } : {},
2065
+ ...fixRecommendation ? { fixRecommendation } : {},
2045
2066
  candidates
2046
2067
  });
2047
2068
  }
2069
+ function fixRecommendationForTop(top, seedNode, legacy) {
2070
+ if (top.classification === "primary-failure" && top.node === seedNode) {
2071
+ return legacy.fixRecommendation;
2072
+ }
2073
+ const name = top.node.replace(/^service:/, "");
2074
+ if (top.classification === "primary-failure") {
2075
+ return `Reduce or throttle the load from ${name} (or scale the saturated downstream capacity it drives) \u2014 the failure originates at this overloading source, not the starved callee.`;
2076
+ }
2077
+ return `${name} is a saturated/starved downstream victim; investigate its inbound load and upstream callers, not its own handler.`;
2078
+ }
2048
2079
 
2049
2080
  // src/policy.ts
2050
2081
  var DEFAULT_ACTION_BY_SEVERITY = {
@@ -5391,7 +5422,7 @@ function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line)
5391
5422
  }
5392
5423
  return sid;
5393
5424
  }
5394
- function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
5425
+ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite, mint = true) {
5395
5426
  const line = callSite.line;
5396
5427
  if (line === void 0) return fileNodeId;
5397
5428
  let sawSymbol = false;
@@ -5406,7 +5437,7 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
5406
5437
  }
5407
5438
  });
5408
5439
  if (candidates.length > 0) return pickContainingSymbol(candidates, callSite.fn);
5409
- if (sawSymbol && callSite.fn) {
5440
+ if (mint && sawSymbol && callSite.fn) {
5410
5441
  return ensureObservedSymbolNode(graph, fileNodeId, service, relPath, callSite.fn, line);
5411
5442
  }
5412
5443
  return fileNodeId;
@@ -5812,6 +5843,20 @@ function incidentAffectedNode(span, graph, scanPath) {
5812
5843
  const callSite = callSiteFromSpan(span, serviceNode, scanPath);
5813
5844
  if (callSite) {
5814
5845
  const relPath = graph ? reconcileObservedRelPath(graph, span.service, callSite.relPath) : callSite.relPath;
5846
+ const canonicalService = serviceNode && typeof serviceNode.name === "string" ? serviceNode.name : span.service;
5847
+ if (graph) {
5848
+ const fusedFileId = (0, import_types8.fileId)(canonicalService, relPath);
5849
+ if (graph.hasNode(fusedFileId)) {
5850
+ return landObservedSymbol(
5851
+ graph,
5852
+ fusedFileId,
5853
+ canonicalService,
5854
+ relPath,
5855
+ { ...callSite, relPath },
5856
+ false
5857
+ );
5858
+ }
5859
+ }
5815
5860
  return (0, import_types8.fileId)(span.service, relPath);
5816
5861
  }
5817
5862
  return sid;
@@ -6175,7 +6220,11 @@ async function handleSpan(ctx, span) {
6175
6220
  ...span.exception?.type ? { exceptionType: span.exception.type } : {},
6176
6221
  ...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
6177
6222
  ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
6178
- affectedNode
6223
+ // Attribute to where the failure originated — the symbol / file / service
6224
+ // the throwing span named (incidentAffectedNode, ADR-191) — the same
6225
+ // source-based attribution the durable receiver write uses, not the
6226
+ // outbound edge target this span happened to mint.
6227
+ affectedNode: incidentAffectedNode(span, ctx.graph, ctx.scanPath)
6179
6228
  };
6180
6229
  await appendErrorEvent(ctx, ev);
6181
6230
  }