@neat.is/core 0.9.9 → 0.9.10
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/{chunk-B6KJG5KN.js → chunk-C3LHXC55.js} +110 -2
- package/dist/chunk-C3LHXC55.js.map +1 -0
- package/dist/{chunk-QETJIA2Q.js → chunk-UHNE7LGF.js} +2 -2
- package/dist/cli.cjs +109 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +109 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +109 -1
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +109 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-B6KJG5KN.js.map +0 -1
- /package/dist/{chunk-QETJIA2Q.js.map → chunk-UHNE7LGF.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
routeSpanToProject,
|
|
3
3
|
startDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-UHNE7LGF.js";
|
|
5
5
|
import {
|
|
6
6
|
ProjectNameCollisionError,
|
|
7
7
|
addProject,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
thresholdForEdgeType,
|
|
38
38
|
touchLastSeen,
|
|
39
39
|
writeAtomically
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-C3LHXC55.js";
|
|
41
41
|
import {
|
|
42
42
|
startOtelGrpcReceiver
|
|
43
43
|
} from "./chunk-ERE47MCR.js";
|
package/dist/neatd.cjs
CHANGED
|
@@ -1828,6 +1828,24 @@ function incidentCountForNode(nodeId, incidents) {
|
|
|
1828
1828
|
if (!incidents || incidents.length === 0) return 0;
|
|
1829
1829
|
return incidents.filter((ev) => incidentMatchesNode(ev, nodeId)).length;
|
|
1830
1830
|
}
|
|
1831
|
+
var DEP_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
1832
|
+
import_types.EdgeType.CALLS,
|
|
1833
|
+
import_types.EdgeType.CONNECTS_TO,
|
|
1834
|
+
import_types.EdgeType.PUBLISHES_TO,
|
|
1835
|
+
import_types.EdgeType.CONSUMES_FROM
|
|
1836
|
+
]);
|
|
1837
|
+
var TIMEOUT_ERROR_PATTERNS = [
|
|
1838
|
+
/\bDEADLINE_EXCEEDED\b/i,
|
|
1839
|
+
/\bdeadline exceeded\b/i,
|
|
1840
|
+
/\bETIMEDOUT\b/i,
|
|
1841
|
+
/\btimed[\s-]?out\b/i,
|
|
1842
|
+
/\btimeout\b/i
|
|
1843
|
+
];
|
|
1844
|
+
function isBoundaryTimeoutError(ev) {
|
|
1845
|
+
if (ev.httpStatusCode === 504) return true;
|
|
1846
|
+
const haystack = [ev.errorType, ev.exceptionType, ev.errorMessage].filter((s) => typeof s === "string").join(" \n ");
|
|
1847
|
+
return TIMEOUT_ERROR_PATTERNS.some((re) => re.test(haystack));
|
|
1848
|
+
}
|
|
1831
1849
|
function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
1832
1850
|
const scope = nodeScope(graph, nodeId);
|
|
1833
1851
|
let errorsFromCallers = 0;
|
|
@@ -1837,6 +1855,8 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
1837
1855
|
let latestInboundMs;
|
|
1838
1856
|
let latencyP95Ms;
|
|
1839
1857
|
let stale = false;
|
|
1858
|
+
let observedErroringDownstream = false;
|
|
1859
|
+
let hasOutboundDeps = false;
|
|
1840
1860
|
for (const n of scope) {
|
|
1841
1861
|
if (!graph.hasNode(n)) continue;
|
|
1842
1862
|
for (const edgeId of graph.inboundEdges(n)) {
|
|
@@ -1858,10 +1878,17 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
1858
1878
|
outboundVolume += e.callCount ?? e.signal?.spanCount ?? 0;
|
|
1859
1879
|
if (e.type === import_types.EdgeType.CALLS) outboundErrors += e.signal?.errorCount ?? 0;
|
|
1860
1880
|
if (e.provenance === import_types.Provenance.STALE) stale = true;
|
|
1881
|
+
if (DEP_EDGE_TYPES.has(e.type)) {
|
|
1882
|
+
hasOutboundDeps = true;
|
|
1883
|
+
if ((e.signal?.errorCount ?? 0) > 0) observedErroringDownstream = true;
|
|
1884
|
+
}
|
|
1861
1885
|
}
|
|
1862
1886
|
}
|
|
1863
1887
|
const errorsEmittedHere = incidentCountForNode(nodeId, incidents) + outboundErrors;
|
|
1864
1888
|
const lastObservedAgeMs = latestInboundMs !== void 0 ? Math.max(0, now - latestInboundMs) : void 0;
|
|
1889
|
+
const boundaryTimeout = (incidents ?? []).some(
|
|
1890
|
+
(ev) => incidentMatchesNode(ev, nodeId) && isBoundaryTimeoutError(ev)
|
|
1891
|
+
);
|
|
1865
1892
|
return {
|
|
1866
1893
|
errorsEmittedHere,
|
|
1867
1894
|
errorsFromCallers,
|
|
@@ -1869,17 +1896,24 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
1869
1896
|
outboundVolume,
|
|
1870
1897
|
...lastObservedAgeMs !== void 0 ? { lastObservedAgeMs } : {},
|
|
1871
1898
|
...latencyP95Ms !== void 0 ? { latencyP95Ms } : {},
|
|
1872
|
-
stale
|
|
1899
|
+
stale,
|
|
1900
|
+
boundaryTimeout,
|
|
1901
|
+
observedErroringDownstream,
|
|
1902
|
+
hasOutboundDeps
|
|
1873
1903
|
};
|
|
1874
1904
|
}
|
|
1875
1905
|
function isSaturated(ctx) {
|
|
1876
1906
|
return ctx.latencyP95Ms !== void 0 && ctx.latencyP95Ms >= SATURATION_P95_MS;
|
|
1877
1907
|
}
|
|
1908
|
+
function isBoundaryTimeoutSymptom(ctx) {
|
|
1909
|
+
return ctx.errorsEmittedHere > 0 && ctx.boundaryTimeout === true && ctx.errorsFromCallers === 0 && ctx.hasOutboundDeps === true && ctx.observedErroringDownstream !== true;
|
|
1910
|
+
}
|
|
1878
1911
|
function classifyNode(ctx) {
|
|
1879
1912
|
if (ctx.errorsEmittedHere > 0) {
|
|
1880
1913
|
if ((ctx.stale || isSaturated(ctx)) && ctx.errorsFromCallers >= ctx.errorsEmittedHere) {
|
|
1881
1914
|
return "symptom-only";
|
|
1882
1915
|
}
|
|
1916
|
+
if (isBoundaryTimeoutSymptom(ctx)) return "symptom-only";
|
|
1883
1917
|
return "primary-failure";
|
|
1884
1918
|
}
|
|
1885
1919
|
if (ctx.errorsFromCallers > 0) return "symptom-only";
|
|
@@ -2084,6 +2118,57 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents, opts) {
|
|
|
2084
2118
|
if (!navigation) return tagged.result;
|
|
2085
2119
|
return enrichWithNavigation(graph, errorNodeId, tagged, incidents, opts?.now ?? Date.now());
|
|
2086
2120
|
}
|
|
2121
|
+
function boundaryIncidentRoute(nodeId, incidents) {
|
|
2122
|
+
for (const ev of incidents ?? []) {
|
|
2123
|
+
if (!incidentMatchesNode(ev, nodeId)) continue;
|
|
2124
|
+
const r = ev.attributes?.["http.route"] ?? ev.attributes?.["http.target"] ?? ev.attributes?.["url.path"];
|
|
2125
|
+
if (typeof r === "string" && r.length > 0) return r;
|
|
2126
|
+
}
|
|
2127
|
+
return void 0;
|
|
2128
|
+
}
|
|
2129
|
+
function declaredOutboundCallees(graph, nodeId) {
|
|
2130
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
2131
|
+
for (const n of nodeScope(graph, nodeId)) {
|
|
2132
|
+
if (!graph.hasNode(n)) continue;
|
|
2133
|
+
for (const edgeId of graph.outboundEdges(n)) {
|
|
2134
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
2135
|
+
if (!DEP_EDGE_TYPES.has(e.type)) continue;
|
|
2136
|
+
if (e.provenance !== import_types.Provenance.EXTRACTED) continue;
|
|
2137
|
+
if (e.target === nodeId || byTarget.has(e.target)) continue;
|
|
2138
|
+
const route = e.evidence?.pathTemplate;
|
|
2139
|
+
byTarget.set(
|
|
2140
|
+
e.target,
|
|
2141
|
+
route !== void 0 ? { target: e.target, route } : { target: e.target }
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
return [...byTarget.values()];
|
|
2146
|
+
}
|
|
2147
|
+
function normalizeRoute(s) {
|
|
2148
|
+
return s.replace(/\/+$/, "").toLowerCase();
|
|
2149
|
+
}
|
|
2150
|
+
function routeMatches(declared, incident) {
|
|
2151
|
+
const a = normalizeRoute(declared);
|
|
2152
|
+
const b = normalizeRoute(incident);
|
|
2153
|
+
return a === b || a.startsWith(b) || b.startsWith(a);
|
|
2154
|
+
}
|
|
2155
|
+
function structuralUpstreamPointer(graph, boundaryNode, incidents) {
|
|
2156
|
+
const route = boundaryIncidentRoute(boundaryNode, incidents);
|
|
2157
|
+
const callees = declaredOutboundCallees(graph, boundaryNode);
|
|
2158
|
+
if (callees.length === 0) return null;
|
|
2159
|
+
const narrowed = route !== void 0 ? callees.filter((c) => c.route !== void 0 && routeMatches(c.route, route)) : callees;
|
|
2160
|
+
const chosen = narrowed.length === 1 ? narrowed[0] : callees.length === 1 ? callees[0] : null;
|
|
2161
|
+
if (!chosen) return null;
|
|
2162
|
+
let node = chosen.target;
|
|
2163
|
+
const seen = /* @__PURE__ */ new Set([boundaryNode, node]);
|
|
2164
|
+
for (let depth = 0; depth < ROOT_CAUSE_MAX_DEPTH; depth++) {
|
|
2165
|
+
const next = declaredOutboundCallees(graph, node);
|
|
2166
|
+
if (next.length !== 1 || seen.has(next[0].target)) break;
|
|
2167
|
+
node = next[0].target;
|
|
2168
|
+
seen.add(node);
|
|
2169
|
+
}
|
|
2170
|
+
return route !== void 0 ? { node, route } : { node };
|
|
2171
|
+
}
|
|
2087
2172
|
function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
2088
2173
|
const legacy = tagged.result;
|
|
2089
2174
|
const seedNode = legacy.rootCauseNode;
|
|
@@ -2117,6 +2202,29 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
|
2117
2202
|
confidence: Math.min(legacy.confidence, 0.4),
|
|
2118
2203
|
...lastProv ? { provenance: lastProv } : {}
|
|
2119
2204
|
});
|
|
2205
|
+
} else if (seedCtx && isBoundaryTimeoutSymptom(seedCtx)) {
|
|
2206
|
+
const pointer = structuralUpstreamPointer(graph, seedNode, incidents);
|
|
2207
|
+
const seedName = displayNameOf(seedNode);
|
|
2208
|
+
const routeNote = pointer?.route ? ` serving ${pointer.route}` : "";
|
|
2209
|
+
if (pointer) {
|
|
2210
|
+
const causeName = displayNameOf(pointer.node);
|
|
2211
|
+
candidates.push({
|
|
2212
|
+
node: pointer.node,
|
|
2213
|
+
classification: "primary-failure",
|
|
2214
|
+
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.`,
|
|
2215
|
+
context: nodeContext(graph, pointer.node, incidents, now),
|
|
2216
|
+
confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.EXTRACTED),
|
|
2217
|
+
provenance: import_types.Provenance.INFERRED
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
candidates.push({
|
|
2221
|
+
node: seedNode,
|
|
2222
|
+
classification: "symptom-only",
|
|
2223
|
+
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.`,
|
|
2224
|
+
context: seedCtx,
|
|
2225
|
+
confidence: Math.min(legacy.confidence, 0.3),
|
|
2226
|
+
...lastProv ? { provenance: lastProv } : {}
|
|
2227
|
+
});
|
|
2120
2228
|
} else if (staleChain) {
|
|
2121
2229
|
const culprit = staleChain.culprit;
|
|
2122
2230
|
const culpritName = displayNameOf(culprit);
|