@neat.is/core 0.9.10-dev.20260830 → 0.9.11
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-3UAUMPIY.js} +114 -22
- package/dist/chunk-3UAUMPIY.js.map +1 -0
- package/dist/{chunk-QETJIA2Q.js → chunk-UNO3X6ZV.js} +2 -2
- package/dist/cli.cjs +113 -21
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +113 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +113 -21
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +131 -21
- 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-UNO3X6ZV.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-UNO3X6ZV.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-3UAUMPIY.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);
|
|
@@ -6422,6 +6530,7 @@ function buildErrorEventForReceiver(span, graph, scanPath) {
|
|
|
6422
6530
|
const ts = span.startTimeIso ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6423
6531
|
const locus = incidentLocus(span, graph, scanPath);
|
|
6424
6532
|
const attrs = withRecoveredCodeAttrs(sanitizeAttributes(span.attributes), locus);
|
|
6533
|
+
const httpStatus = httpResponseStatus(span);
|
|
6425
6534
|
return {
|
|
6426
6535
|
id: `${span.traceId}:${span.spanId}`,
|
|
6427
6536
|
timestamp: ts,
|
|
@@ -6431,6 +6540,7 @@ function buildErrorEventForReceiver(span, graph, scanPath) {
|
|
|
6431
6540
|
errorMessage: incidentMessage(span),
|
|
6432
6541
|
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
6433
6542
|
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
6543
|
+
...httpStatus !== void 0 ? { httpStatusCode: httpStatus } : {},
|
|
6434
6544
|
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
6435
6545
|
affectedNode: locus.affectedNode
|
|
6436
6546
|
};
|
|
@@ -6782,26 +6892,8 @@ async function handleSpan(ctx, span) {
|
|
|
6782
6892
|
if (span.statusCode === 2) {
|
|
6783
6893
|
stitchTrace(ctx.graph, sourceId, ts);
|
|
6784
6894
|
if (ctx.writeErrorEventInline !== false) {
|
|
6785
|
-
const
|
|
6786
|
-
|
|
6787
|
-
const ev = {
|
|
6788
|
-
id: `${span.traceId}:${span.spanId}`,
|
|
6789
|
-
timestamp: ts,
|
|
6790
|
-
service: span.service,
|
|
6791
|
-
traceId: span.traceId,
|
|
6792
|
-
spanId: span.spanId,
|
|
6793
|
-
errorMessage: incidentMessage(span),
|
|
6794
|
-
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
6795
|
-
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
6796
|
-
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
6797
|
-
// Attribute to where the failure originated — the symbol / file / service
|
|
6798
|
-
// the throwing span named (incidentAffectedNode / ADR-191, extended to
|
|
6799
|
-
// recover the locus from the stacktrace when the span stamped no code.*
|
|
6800
|
-
// attrs, ADR-216) — the same source-based attribution the durable
|
|
6801
|
-
// receiver write uses, not the outbound edge target this span minted.
|
|
6802
|
-
affectedNode: locus.affectedNode
|
|
6803
|
-
};
|
|
6804
|
-
await appendErrorEvent(ctx, ev);
|
|
6895
|
+
const ev = buildErrorEventForReceiver(span, ctx.graph, ctx.scanPath);
|
|
6896
|
+
if (ev) await appendErrorEvent(ctx, ev);
|
|
6805
6897
|
}
|
|
6806
6898
|
}
|
|
6807
6899
|
if (span.statusCode !== 2) {
|