@neat.is/core 0.9.10-dev.20260830 → 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
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
startStalenessLoop,
|
|
21
21
|
touchLastSeen,
|
|
22
22
|
writeAtomically
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-C3LHXC55.js";
|
|
24
24
|
import {
|
|
25
25
|
assertBindAuthority,
|
|
26
26
|
buildOtelReceiver,
|
|
@@ -901,4 +901,4 @@ export {
|
|
|
901
901
|
resolveHost,
|
|
902
902
|
startDaemon
|
|
903
903
|
};
|
|
904
|
-
//# sourceMappingURL=chunk-
|
|
904
|
+
//# sourceMappingURL=chunk-UHNE7LGF.js.map
|
package/dist/cli.cjs
CHANGED
|
@@ -1873,6 +1873,24 @@ function incidentCountForNode(nodeId, incidents) {
|
|
|
1873
1873
|
if (!incidents || incidents.length === 0) return 0;
|
|
1874
1874
|
return incidents.filter((ev) => incidentMatchesNode(ev, nodeId)).length;
|
|
1875
1875
|
}
|
|
1876
|
+
var DEP_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
1877
|
+
import_types.EdgeType.CALLS,
|
|
1878
|
+
import_types.EdgeType.CONNECTS_TO,
|
|
1879
|
+
import_types.EdgeType.PUBLISHES_TO,
|
|
1880
|
+
import_types.EdgeType.CONSUMES_FROM
|
|
1881
|
+
]);
|
|
1882
|
+
var TIMEOUT_ERROR_PATTERNS = [
|
|
1883
|
+
/\bDEADLINE_EXCEEDED\b/i,
|
|
1884
|
+
/\bdeadline exceeded\b/i,
|
|
1885
|
+
/\bETIMEDOUT\b/i,
|
|
1886
|
+
/\btimed[\s-]?out\b/i,
|
|
1887
|
+
/\btimeout\b/i
|
|
1888
|
+
];
|
|
1889
|
+
function isBoundaryTimeoutError(ev) {
|
|
1890
|
+
if (ev.httpStatusCode === 504) return true;
|
|
1891
|
+
const haystack = [ev.errorType, ev.exceptionType, ev.errorMessage].filter((s) => typeof s === "string").join(" \n ");
|
|
1892
|
+
return TIMEOUT_ERROR_PATTERNS.some((re) => re.test(haystack));
|
|
1893
|
+
}
|
|
1876
1894
|
function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
1877
1895
|
const scope = nodeScope(graph, nodeId);
|
|
1878
1896
|
let errorsFromCallers = 0;
|
|
@@ -1882,6 +1900,8 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
1882
1900
|
let latestInboundMs;
|
|
1883
1901
|
let latencyP95Ms;
|
|
1884
1902
|
let stale = false;
|
|
1903
|
+
let observedErroringDownstream = false;
|
|
1904
|
+
let hasOutboundDeps = false;
|
|
1885
1905
|
for (const n of scope) {
|
|
1886
1906
|
if (!graph.hasNode(n)) continue;
|
|
1887
1907
|
for (const edgeId of graph.inboundEdges(n)) {
|
|
@@ -1903,10 +1923,17 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
1903
1923
|
outboundVolume += e.callCount ?? e.signal?.spanCount ?? 0;
|
|
1904
1924
|
if (e.type === import_types.EdgeType.CALLS) outboundErrors += e.signal?.errorCount ?? 0;
|
|
1905
1925
|
if (e.provenance === import_types.Provenance.STALE) stale = true;
|
|
1926
|
+
if (DEP_EDGE_TYPES.has(e.type)) {
|
|
1927
|
+
hasOutboundDeps = true;
|
|
1928
|
+
if ((e.signal?.errorCount ?? 0) > 0) observedErroringDownstream = true;
|
|
1929
|
+
}
|
|
1906
1930
|
}
|
|
1907
1931
|
}
|
|
1908
1932
|
const errorsEmittedHere = incidentCountForNode(nodeId, incidents) + outboundErrors;
|
|
1909
1933
|
const lastObservedAgeMs = latestInboundMs !== void 0 ? Math.max(0, now - latestInboundMs) : void 0;
|
|
1934
|
+
const boundaryTimeout = (incidents ?? []).some(
|
|
1935
|
+
(ev) => incidentMatchesNode(ev, nodeId) && isBoundaryTimeoutError(ev)
|
|
1936
|
+
);
|
|
1910
1937
|
return {
|
|
1911
1938
|
errorsEmittedHere,
|
|
1912
1939
|
errorsFromCallers,
|
|
@@ -1914,17 +1941,24 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
1914
1941
|
outboundVolume,
|
|
1915
1942
|
...lastObservedAgeMs !== void 0 ? { lastObservedAgeMs } : {},
|
|
1916
1943
|
...latencyP95Ms !== void 0 ? { latencyP95Ms } : {},
|
|
1917
|
-
stale
|
|
1944
|
+
stale,
|
|
1945
|
+
boundaryTimeout,
|
|
1946
|
+
observedErroringDownstream,
|
|
1947
|
+
hasOutboundDeps
|
|
1918
1948
|
};
|
|
1919
1949
|
}
|
|
1920
1950
|
function isSaturated(ctx) {
|
|
1921
1951
|
return ctx.latencyP95Ms !== void 0 && ctx.latencyP95Ms >= SATURATION_P95_MS;
|
|
1922
1952
|
}
|
|
1953
|
+
function isBoundaryTimeoutSymptom(ctx) {
|
|
1954
|
+
return ctx.errorsEmittedHere > 0 && ctx.boundaryTimeout === true && ctx.errorsFromCallers === 0 && ctx.hasOutboundDeps === true && ctx.observedErroringDownstream !== true;
|
|
1955
|
+
}
|
|
1923
1956
|
function classifyNode(ctx) {
|
|
1924
1957
|
if (ctx.errorsEmittedHere > 0) {
|
|
1925
1958
|
if ((ctx.stale || isSaturated(ctx)) && ctx.errorsFromCallers >= ctx.errorsEmittedHere) {
|
|
1926
1959
|
return "symptom-only";
|
|
1927
1960
|
}
|
|
1961
|
+
if (isBoundaryTimeoutSymptom(ctx)) return "symptom-only";
|
|
1928
1962
|
return "primary-failure";
|
|
1929
1963
|
}
|
|
1930
1964
|
if (ctx.errorsFromCallers > 0) return "symptom-only";
|
|
@@ -2129,6 +2163,57 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents, opts) {
|
|
|
2129
2163
|
if (!navigation) return tagged.result;
|
|
2130
2164
|
return enrichWithNavigation(graph, errorNodeId, tagged, incidents, opts?.now ?? Date.now());
|
|
2131
2165
|
}
|
|
2166
|
+
function boundaryIncidentRoute(nodeId, incidents) {
|
|
2167
|
+
for (const ev of incidents ?? []) {
|
|
2168
|
+
if (!incidentMatchesNode(ev, nodeId)) continue;
|
|
2169
|
+
const r = ev.attributes?.["http.route"] ?? ev.attributes?.["http.target"] ?? ev.attributes?.["url.path"];
|
|
2170
|
+
if (typeof r === "string" && r.length > 0) return r;
|
|
2171
|
+
}
|
|
2172
|
+
return void 0;
|
|
2173
|
+
}
|
|
2174
|
+
function declaredOutboundCallees(graph, nodeId) {
|
|
2175
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
2176
|
+
for (const n of nodeScope(graph, nodeId)) {
|
|
2177
|
+
if (!graph.hasNode(n)) continue;
|
|
2178
|
+
for (const edgeId of graph.outboundEdges(n)) {
|
|
2179
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
2180
|
+
if (!DEP_EDGE_TYPES.has(e.type)) continue;
|
|
2181
|
+
if (e.provenance !== import_types.Provenance.EXTRACTED) continue;
|
|
2182
|
+
if (e.target === nodeId || byTarget.has(e.target)) continue;
|
|
2183
|
+
const route = e.evidence?.pathTemplate;
|
|
2184
|
+
byTarget.set(
|
|
2185
|
+
e.target,
|
|
2186
|
+
route !== void 0 ? { target: e.target, route } : { target: e.target }
|
|
2187
|
+
);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
return [...byTarget.values()];
|
|
2191
|
+
}
|
|
2192
|
+
function normalizeRoute(s) {
|
|
2193
|
+
return s.replace(/\/+$/, "").toLowerCase();
|
|
2194
|
+
}
|
|
2195
|
+
function routeMatches(declared, incident) {
|
|
2196
|
+
const a = normalizeRoute(declared);
|
|
2197
|
+
const b = normalizeRoute(incident);
|
|
2198
|
+
return a === b || a.startsWith(b) || b.startsWith(a);
|
|
2199
|
+
}
|
|
2200
|
+
function structuralUpstreamPointer(graph, boundaryNode, incidents) {
|
|
2201
|
+
const route = boundaryIncidentRoute(boundaryNode, incidents);
|
|
2202
|
+
const callees = declaredOutboundCallees(graph, boundaryNode);
|
|
2203
|
+
if (callees.length === 0) return null;
|
|
2204
|
+
const narrowed = route !== void 0 ? callees.filter((c) => c.route !== void 0 && routeMatches(c.route, route)) : callees;
|
|
2205
|
+
const chosen = narrowed.length === 1 ? narrowed[0] : callees.length === 1 ? callees[0] : null;
|
|
2206
|
+
if (!chosen) return null;
|
|
2207
|
+
let node = chosen.target;
|
|
2208
|
+
const seen = /* @__PURE__ */ new Set([boundaryNode, node]);
|
|
2209
|
+
for (let depth = 0; depth < ROOT_CAUSE_MAX_DEPTH; depth++) {
|
|
2210
|
+
const next = declaredOutboundCallees(graph, node);
|
|
2211
|
+
if (next.length !== 1 || seen.has(next[0].target)) break;
|
|
2212
|
+
node = next[0].target;
|
|
2213
|
+
seen.add(node);
|
|
2214
|
+
}
|
|
2215
|
+
return route !== void 0 ? { node, route } : { node };
|
|
2216
|
+
}
|
|
2132
2217
|
function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
2133
2218
|
const legacy = tagged.result;
|
|
2134
2219
|
const seedNode = legacy.rootCauseNode;
|
|
@@ -2162,6 +2247,29 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
|
2162
2247
|
confidence: Math.min(legacy.confidence, 0.4),
|
|
2163
2248
|
...lastProv ? { provenance: lastProv } : {}
|
|
2164
2249
|
});
|
|
2250
|
+
} else if (seedCtx && isBoundaryTimeoutSymptom(seedCtx)) {
|
|
2251
|
+
const pointer = structuralUpstreamPointer(graph, seedNode, incidents);
|
|
2252
|
+
const seedName = displayNameOf(seedNode);
|
|
2253
|
+
const routeNote = pointer?.route ? ` serving ${pointer.route}` : "";
|
|
2254
|
+
if (pointer) {
|
|
2255
|
+
const causeName = displayNameOf(pointer.node);
|
|
2256
|
+
candidates.push({
|
|
2257
|
+
node: pointer.node,
|
|
2258
|
+
classification: "primary-failure",
|
|
2259
|
+
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.`,
|
|
2260
|
+
context: nodeContext(graph, pointer.node, incidents, now),
|
|
2261
|
+
confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.EXTRACTED),
|
|
2262
|
+
provenance: import_types.Provenance.INFERRED
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
candidates.push({
|
|
2266
|
+
node: seedNode,
|
|
2267
|
+
classification: "symptom-only",
|
|
2268
|
+
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.`,
|
|
2269
|
+
context: seedCtx,
|
|
2270
|
+
confidence: Math.min(legacy.confidence, 0.3),
|
|
2271
|
+
...lastProv ? { provenance: lastProv } : {}
|
|
2272
|
+
});
|
|
2165
2273
|
} else if (staleChain) {
|
|
2166
2274
|
const culprit = staleChain.culprit;
|
|
2167
2275
|
const culpritName = displayNameOf(culprit);
|