@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/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
resolveHost,
|
|
7
7
|
resolveNeatVersion,
|
|
8
8
|
writeDaemonRecord
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-UHNE7LGF.js";
|
|
10
10
|
import {
|
|
11
11
|
buildSearchIndex
|
|
12
12
|
} from "./chunk-BC53SCT7.js";
|
|
@@ -75,7 +75,7 @@ import {
|
|
|
75
75
|
startStalenessLoop,
|
|
76
76
|
upsertConnectorEntry,
|
|
77
77
|
validateConnectorEntry
|
|
78
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-C3LHXC55.js";
|
|
79
79
|
import {
|
|
80
80
|
startOtelGrpcReceiver
|
|
81
81
|
} from "./chunk-ERE47MCR.js";
|
package/dist/index.cjs
CHANGED
|
@@ -1865,6 +1865,24 @@ function incidentCountForNode(nodeId, incidents) {
|
|
|
1865
1865
|
if (!incidents || incidents.length === 0) return 0;
|
|
1866
1866
|
return incidents.filter((ev) => incidentMatchesNode(ev, nodeId)).length;
|
|
1867
1867
|
}
|
|
1868
|
+
var DEP_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
1869
|
+
import_types.EdgeType.CALLS,
|
|
1870
|
+
import_types.EdgeType.CONNECTS_TO,
|
|
1871
|
+
import_types.EdgeType.PUBLISHES_TO,
|
|
1872
|
+
import_types.EdgeType.CONSUMES_FROM
|
|
1873
|
+
]);
|
|
1874
|
+
var TIMEOUT_ERROR_PATTERNS = [
|
|
1875
|
+
/\bDEADLINE_EXCEEDED\b/i,
|
|
1876
|
+
/\bdeadline exceeded\b/i,
|
|
1877
|
+
/\bETIMEDOUT\b/i,
|
|
1878
|
+
/\btimed[\s-]?out\b/i,
|
|
1879
|
+
/\btimeout\b/i
|
|
1880
|
+
];
|
|
1881
|
+
function isBoundaryTimeoutError(ev) {
|
|
1882
|
+
if (ev.httpStatusCode === 504) return true;
|
|
1883
|
+
const haystack = [ev.errorType, ev.exceptionType, ev.errorMessage].filter((s) => typeof s === "string").join(" \n ");
|
|
1884
|
+
return TIMEOUT_ERROR_PATTERNS.some((re) => re.test(haystack));
|
|
1885
|
+
}
|
|
1868
1886
|
function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
1869
1887
|
const scope = nodeScope(graph, nodeId);
|
|
1870
1888
|
let errorsFromCallers = 0;
|
|
@@ -1874,6 +1892,8 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
1874
1892
|
let latestInboundMs;
|
|
1875
1893
|
let latencyP95Ms;
|
|
1876
1894
|
let stale = false;
|
|
1895
|
+
let observedErroringDownstream = false;
|
|
1896
|
+
let hasOutboundDeps = false;
|
|
1877
1897
|
for (const n of scope) {
|
|
1878
1898
|
if (!graph.hasNode(n)) continue;
|
|
1879
1899
|
for (const edgeId of graph.inboundEdges(n)) {
|
|
@@ -1895,10 +1915,17 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
1895
1915
|
outboundVolume += e.callCount ?? e.signal?.spanCount ?? 0;
|
|
1896
1916
|
if (e.type === import_types.EdgeType.CALLS) outboundErrors += e.signal?.errorCount ?? 0;
|
|
1897
1917
|
if (e.provenance === import_types.Provenance.STALE) stale = true;
|
|
1918
|
+
if (DEP_EDGE_TYPES.has(e.type)) {
|
|
1919
|
+
hasOutboundDeps = true;
|
|
1920
|
+
if ((e.signal?.errorCount ?? 0) > 0) observedErroringDownstream = true;
|
|
1921
|
+
}
|
|
1898
1922
|
}
|
|
1899
1923
|
}
|
|
1900
1924
|
const errorsEmittedHere = incidentCountForNode(nodeId, incidents) + outboundErrors;
|
|
1901
1925
|
const lastObservedAgeMs = latestInboundMs !== void 0 ? Math.max(0, now - latestInboundMs) : void 0;
|
|
1926
|
+
const boundaryTimeout = (incidents ?? []).some(
|
|
1927
|
+
(ev) => incidentMatchesNode(ev, nodeId) && isBoundaryTimeoutError(ev)
|
|
1928
|
+
);
|
|
1902
1929
|
return {
|
|
1903
1930
|
errorsEmittedHere,
|
|
1904
1931
|
errorsFromCallers,
|
|
@@ -1906,17 +1933,24 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
1906
1933
|
outboundVolume,
|
|
1907
1934
|
...lastObservedAgeMs !== void 0 ? { lastObservedAgeMs } : {},
|
|
1908
1935
|
...latencyP95Ms !== void 0 ? { latencyP95Ms } : {},
|
|
1909
|
-
stale
|
|
1936
|
+
stale,
|
|
1937
|
+
boundaryTimeout,
|
|
1938
|
+
observedErroringDownstream,
|
|
1939
|
+
hasOutboundDeps
|
|
1910
1940
|
};
|
|
1911
1941
|
}
|
|
1912
1942
|
function isSaturated(ctx) {
|
|
1913
1943
|
return ctx.latencyP95Ms !== void 0 && ctx.latencyP95Ms >= SATURATION_P95_MS;
|
|
1914
1944
|
}
|
|
1945
|
+
function isBoundaryTimeoutSymptom(ctx) {
|
|
1946
|
+
return ctx.errorsEmittedHere > 0 && ctx.boundaryTimeout === true && ctx.errorsFromCallers === 0 && ctx.hasOutboundDeps === true && ctx.observedErroringDownstream !== true;
|
|
1947
|
+
}
|
|
1915
1948
|
function classifyNode(ctx) {
|
|
1916
1949
|
if (ctx.errorsEmittedHere > 0) {
|
|
1917
1950
|
if ((ctx.stale || isSaturated(ctx)) && ctx.errorsFromCallers >= ctx.errorsEmittedHere) {
|
|
1918
1951
|
return "symptom-only";
|
|
1919
1952
|
}
|
|
1953
|
+
if (isBoundaryTimeoutSymptom(ctx)) return "symptom-only";
|
|
1920
1954
|
return "primary-failure";
|
|
1921
1955
|
}
|
|
1922
1956
|
if (ctx.errorsFromCallers > 0) return "symptom-only";
|
|
@@ -2121,6 +2155,57 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents, opts) {
|
|
|
2121
2155
|
if (!navigation) return tagged.result;
|
|
2122
2156
|
return enrichWithNavigation(graph, errorNodeId, tagged, incidents, opts?.now ?? Date.now());
|
|
2123
2157
|
}
|
|
2158
|
+
function boundaryIncidentRoute(nodeId, incidents) {
|
|
2159
|
+
for (const ev of incidents ?? []) {
|
|
2160
|
+
if (!incidentMatchesNode(ev, nodeId)) continue;
|
|
2161
|
+
const r = ev.attributes?.["http.route"] ?? ev.attributes?.["http.target"] ?? ev.attributes?.["url.path"];
|
|
2162
|
+
if (typeof r === "string" && r.length > 0) return r;
|
|
2163
|
+
}
|
|
2164
|
+
return void 0;
|
|
2165
|
+
}
|
|
2166
|
+
function declaredOutboundCallees(graph, nodeId) {
|
|
2167
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
2168
|
+
for (const n of nodeScope(graph, nodeId)) {
|
|
2169
|
+
if (!graph.hasNode(n)) continue;
|
|
2170
|
+
for (const edgeId of graph.outboundEdges(n)) {
|
|
2171
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
2172
|
+
if (!DEP_EDGE_TYPES.has(e.type)) continue;
|
|
2173
|
+
if (e.provenance !== import_types.Provenance.EXTRACTED) continue;
|
|
2174
|
+
if (e.target === nodeId || byTarget.has(e.target)) continue;
|
|
2175
|
+
const route = e.evidence?.pathTemplate;
|
|
2176
|
+
byTarget.set(
|
|
2177
|
+
e.target,
|
|
2178
|
+
route !== void 0 ? { target: e.target, route } : { target: e.target }
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
return [...byTarget.values()];
|
|
2183
|
+
}
|
|
2184
|
+
function normalizeRoute(s) {
|
|
2185
|
+
return s.replace(/\/+$/, "").toLowerCase();
|
|
2186
|
+
}
|
|
2187
|
+
function routeMatches(declared, incident) {
|
|
2188
|
+
const a = normalizeRoute(declared);
|
|
2189
|
+
const b = normalizeRoute(incident);
|
|
2190
|
+
return a === b || a.startsWith(b) || b.startsWith(a);
|
|
2191
|
+
}
|
|
2192
|
+
function structuralUpstreamPointer(graph, boundaryNode, incidents) {
|
|
2193
|
+
const route = boundaryIncidentRoute(boundaryNode, incidents);
|
|
2194
|
+
const callees = declaredOutboundCallees(graph, boundaryNode);
|
|
2195
|
+
if (callees.length === 0) return null;
|
|
2196
|
+
const narrowed = route !== void 0 ? callees.filter((c) => c.route !== void 0 && routeMatches(c.route, route)) : callees;
|
|
2197
|
+
const chosen = narrowed.length === 1 ? narrowed[0] : callees.length === 1 ? callees[0] : null;
|
|
2198
|
+
if (!chosen) return null;
|
|
2199
|
+
let node = chosen.target;
|
|
2200
|
+
const seen = /* @__PURE__ */ new Set([boundaryNode, node]);
|
|
2201
|
+
for (let depth = 0; depth < ROOT_CAUSE_MAX_DEPTH; depth++) {
|
|
2202
|
+
const next = declaredOutboundCallees(graph, node);
|
|
2203
|
+
if (next.length !== 1 || seen.has(next[0].target)) break;
|
|
2204
|
+
node = next[0].target;
|
|
2205
|
+
seen.add(node);
|
|
2206
|
+
}
|
|
2207
|
+
return route !== void 0 ? { node, route } : { node };
|
|
2208
|
+
}
|
|
2124
2209
|
function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
2125
2210
|
const legacy = tagged.result;
|
|
2126
2211
|
const seedNode = legacy.rootCauseNode;
|
|
@@ -2154,6 +2239,29 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
|
2154
2239
|
confidence: Math.min(legacy.confidence, 0.4),
|
|
2155
2240
|
...lastProv ? { provenance: lastProv } : {}
|
|
2156
2241
|
});
|
|
2242
|
+
} else if (seedCtx && isBoundaryTimeoutSymptom(seedCtx)) {
|
|
2243
|
+
const pointer = structuralUpstreamPointer(graph, seedNode, incidents);
|
|
2244
|
+
const seedName = displayNameOf(seedNode);
|
|
2245
|
+
const routeNote = pointer?.route ? ` serving ${pointer.route}` : "";
|
|
2246
|
+
if (pointer) {
|
|
2247
|
+
const causeName = displayNameOf(pointer.node);
|
|
2248
|
+
candidates.push({
|
|
2249
|
+
node: pointer.node,
|
|
2250
|
+
classification: "primary-failure",
|
|
2251
|
+
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.`,
|
|
2252
|
+
context: nodeContext(graph, pointer.node, incidents, now),
|
|
2253
|
+
confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.EXTRACTED),
|
|
2254
|
+
provenance: import_types.Provenance.INFERRED
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
candidates.push({
|
|
2258
|
+
node: seedNode,
|
|
2259
|
+
classification: "symptom-only",
|
|
2260
|
+
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.`,
|
|
2261
|
+
context: seedCtx,
|
|
2262
|
+
confidence: Math.min(legacy.confidence, 0.3),
|
|
2263
|
+
...lastProv ? { provenance: lastProv } : {}
|
|
2264
|
+
});
|
|
2157
2265
|
} else if (staleChain) {
|
|
2158
2266
|
const culprit = staleChain.culprit;
|
|
2159
2267
|
const culpritName = displayNameOf(culprit);
|