@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/neatd.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
reconcileDaemonRecordSync,
|
|
4
4
|
startDaemon
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-UNO3X6ZV.js";
|
|
6
6
|
import {
|
|
7
7
|
listProjects,
|
|
8
8
|
registryPath
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-3UAUMPIY.js";
|
|
10
10
|
import {
|
|
11
11
|
BindAuthorityError,
|
|
12
12
|
__require
|
package/dist/server.cjs
CHANGED
|
@@ -5704,6 +5704,26 @@ function sanitizeAttributes(attrs) {
|
|
|
5704
5704
|
}
|
|
5705
5705
|
return out;
|
|
5706
5706
|
}
|
|
5707
|
+
function buildErrorEventForReceiver(span, graph, scanPath) {
|
|
5708
|
+
if (span.statusCode !== 2) return null;
|
|
5709
|
+
const ts = span.startTimeIso ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
5710
|
+
const locus = incidentLocus(span, graph, scanPath);
|
|
5711
|
+
const attrs = withRecoveredCodeAttrs(sanitizeAttributes(span.attributes), locus);
|
|
5712
|
+
const httpStatus = httpResponseStatus(span);
|
|
5713
|
+
return {
|
|
5714
|
+
id: `${span.traceId}:${span.spanId}`,
|
|
5715
|
+
timestamp: ts,
|
|
5716
|
+
service: span.service,
|
|
5717
|
+
traceId: span.traceId,
|
|
5718
|
+
spanId: span.spanId,
|
|
5719
|
+
errorMessage: incidentMessage(span),
|
|
5720
|
+
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
5721
|
+
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
5722
|
+
...httpStatus !== void 0 ? { httpStatusCode: httpStatus } : {},
|
|
5723
|
+
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
5724
|
+
affectedNode: locus.affectedNode
|
|
5725
|
+
};
|
|
5726
|
+
}
|
|
5707
5727
|
async function recordFailingResponseIncident(ctx, span, affectedNode, timestamp, statusCode, count, firstTimestamp) {
|
|
5708
5728
|
const attrs = sanitizeAttributes(span.attributes);
|
|
5709
5729
|
const first = firstTimestamp ?? timestamp;
|
|
@@ -6042,26 +6062,8 @@ async function handleSpan(ctx, span) {
|
|
|
6042
6062
|
if (span.statusCode === 2) {
|
|
6043
6063
|
stitchTrace(ctx.graph, sourceId, ts);
|
|
6044
6064
|
if (ctx.writeErrorEventInline !== false) {
|
|
6045
|
-
const
|
|
6046
|
-
|
|
6047
|
-
const ev = {
|
|
6048
|
-
id: `${span.traceId}:${span.spanId}`,
|
|
6049
|
-
timestamp: ts,
|
|
6050
|
-
service: span.service,
|
|
6051
|
-
traceId: span.traceId,
|
|
6052
|
-
spanId: span.spanId,
|
|
6053
|
-
errorMessage: incidentMessage(span),
|
|
6054
|
-
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
6055
|
-
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
6056
|
-
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
6057
|
-
// Attribute to where the failure originated — the symbol / file / service
|
|
6058
|
-
// the throwing span named (incidentAffectedNode / ADR-191, extended to
|
|
6059
|
-
// recover the locus from the stacktrace when the span stamped no code.*
|
|
6060
|
-
// attrs, ADR-216) — the same source-based attribution the durable
|
|
6061
|
-
// receiver write uses, not the outbound edge target this span minted.
|
|
6062
|
-
affectedNode: locus.affectedNode
|
|
6063
|
-
};
|
|
6064
|
-
await appendErrorEvent(ctx, ev);
|
|
6065
|
+
const ev = buildErrorEventForReceiver(span, ctx.graph, ctx.scanPath);
|
|
6066
|
+
if (ev) await appendErrorEvent(ctx, ev);
|
|
6065
6067
|
}
|
|
6066
6068
|
}
|
|
6067
6069
|
if (span.statusCode !== 2) {
|
|
@@ -6962,6 +6964,24 @@ function incidentCountForNode(nodeId, incidents) {
|
|
|
6962
6964
|
if (!incidents || incidents.length === 0) return 0;
|
|
6963
6965
|
return incidents.filter((ev) => incidentMatchesNode(ev, nodeId)).length;
|
|
6964
6966
|
}
|
|
6967
|
+
var DEP_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
6968
|
+
import_types8.EdgeType.CALLS,
|
|
6969
|
+
import_types8.EdgeType.CONNECTS_TO,
|
|
6970
|
+
import_types8.EdgeType.PUBLISHES_TO,
|
|
6971
|
+
import_types8.EdgeType.CONSUMES_FROM
|
|
6972
|
+
]);
|
|
6973
|
+
var TIMEOUT_ERROR_PATTERNS = [
|
|
6974
|
+
/\bDEADLINE_EXCEEDED\b/i,
|
|
6975
|
+
/\bdeadline exceeded\b/i,
|
|
6976
|
+
/\bETIMEDOUT\b/i,
|
|
6977
|
+
/\btimed[\s-]?out\b/i,
|
|
6978
|
+
/\btimeout\b/i
|
|
6979
|
+
];
|
|
6980
|
+
function isBoundaryTimeoutError(ev) {
|
|
6981
|
+
if (ev.httpStatusCode === 504) return true;
|
|
6982
|
+
const haystack = [ev.errorType, ev.exceptionType, ev.errorMessage].filter((s) => typeof s === "string").join(" \n ");
|
|
6983
|
+
return TIMEOUT_ERROR_PATTERNS.some((re) => re.test(haystack));
|
|
6984
|
+
}
|
|
6965
6985
|
function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
6966
6986
|
const scope = nodeScope(graph, nodeId);
|
|
6967
6987
|
let errorsFromCallers = 0;
|
|
@@ -6971,6 +6991,8 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
6971
6991
|
let latestInboundMs;
|
|
6972
6992
|
let latencyP95Ms;
|
|
6973
6993
|
let stale = false;
|
|
6994
|
+
let observedErroringDownstream = false;
|
|
6995
|
+
let hasOutboundDeps = false;
|
|
6974
6996
|
for (const n of scope) {
|
|
6975
6997
|
if (!graph.hasNode(n)) continue;
|
|
6976
6998
|
for (const edgeId of graph.inboundEdges(n)) {
|
|
@@ -6992,10 +7014,17 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
6992
7014
|
outboundVolume += e.callCount ?? e.signal?.spanCount ?? 0;
|
|
6993
7015
|
if (e.type === import_types8.EdgeType.CALLS) outboundErrors += e.signal?.errorCount ?? 0;
|
|
6994
7016
|
if (e.provenance === import_types8.Provenance.STALE) stale = true;
|
|
7017
|
+
if (DEP_EDGE_TYPES.has(e.type)) {
|
|
7018
|
+
hasOutboundDeps = true;
|
|
7019
|
+
if ((e.signal?.errorCount ?? 0) > 0) observedErroringDownstream = true;
|
|
7020
|
+
}
|
|
6995
7021
|
}
|
|
6996
7022
|
}
|
|
6997
7023
|
const errorsEmittedHere = incidentCountForNode(nodeId, incidents) + outboundErrors;
|
|
6998
7024
|
const lastObservedAgeMs = latestInboundMs !== void 0 ? Math.max(0, now - latestInboundMs) : void 0;
|
|
7025
|
+
const boundaryTimeout = (incidents ?? []).some(
|
|
7026
|
+
(ev) => incidentMatchesNode(ev, nodeId) && isBoundaryTimeoutError(ev)
|
|
7027
|
+
);
|
|
6999
7028
|
return {
|
|
7000
7029
|
errorsEmittedHere,
|
|
7001
7030
|
errorsFromCallers,
|
|
@@ -7003,17 +7032,24 @@ function nodeContext(graph, nodeId, incidents, now = Date.now()) {
|
|
|
7003
7032
|
outboundVolume,
|
|
7004
7033
|
...lastObservedAgeMs !== void 0 ? { lastObservedAgeMs } : {},
|
|
7005
7034
|
...latencyP95Ms !== void 0 ? { latencyP95Ms } : {},
|
|
7006
|
-
stale
|
|
7035
|
+
stale,
|
|
7036
|
+
boundaryTimeout,
|
|
7037
|
+
observedErroringDownstream,
|
|
7038
|
+
hasOutboundDeps
|
|
7007
7039
|
};
|
|
7008
7040
|
}
|
|
7009
7041
|
function isSaturated(ctx) {
|
|
7010
7042
|
return ctx.latencyP95Ms !== void 0 && ctx.latencyP95Ms >= SATURATION_P95_MS;
|
|
7011
7043
|
}
|
|
7044
|
+
function isBoundaryTimeoutSymptom(ctx) {
|
|
7045
|
+
return ctx.errorsEmittedHere > 0 && ctx.boundaryTimeout === true && ctx.errorsFromCallers === 0 && ctx.hasOutboundDeps === true && ctx.observedErroringDownstream !== true;
|
|
7046
|
+
}
|
|
7012
7047
|
function classifyNode(ctx) {
|
|
7013
7048
|
if (ctx.errorsEmittedHere > 0) {
|
|
7014
7049
|
if ((ctx.stale || isSaturated(ctx)) && ctx.errorsFromCallers >= ctx.errorsEmittedHere) {
|
|
7015
7050
|
return "symptom-only";
|
|
7016
7051
|
}
|
|
7052
|
+
if (isBoundaryTimeoutSymptom(ctx)) return "symptom-only";
|
|
7017
7053
|
return "primary-failure";
|
|
7018
7054
|
}
|
|
7019
7055
|
if (ctx.errorsFromCallers > 0) return "symptom-only";
|
|
@@ -7218,6 +7254,57 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents, opts) {
|
|
|
7218
7254
|
if (!navigation) return tagged.result;
|
|
7219
7255
|
return enrichWithNavigation(graph, errorNodeId, tagged, incidents, opts?.now ?? Date.now());
|
|
7220
7256
|
}
|
|
7257
|
+
function boundaryIncidentRoute(nodeId, incidents) {
|
|
7258
|
+
for (const ev of incidents ?? []) {
|
|
7259
|
+
if (!incidentMatchesNode(ev, nodeId)) continue;
|
|
7260
|
+
const r = ev.attributes?.["http.route"] ?? ev.attributes?.["http.target"] ?? ev.attributes?.["url.path"];
|
|
7261
|
+
if (typeof r === "string" && r.length > 0) return r;
|
|
7262
|
+
}
|
|
7263
|
+
return void 0;
|
|
7264
|
+
}
|
|
7265
|
+
function declaredOutboundCallees(graph, nodeId) {
|
|
7266
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
7267
|
+
for (const n of nodeScope(graph, nodeId)) {
|
|
7268
|
+
if (!graph.hasNode(n)) continue;
|
|
7269
|
+
for (const edgeId of graph.outboundEdges(n)) {
|
|
7270
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
7271
|
+
if (!DEP_EDGE_TYPES.has(e.type)) continue;
|
|
7272
|
+
if (e.provenance !== import_types8.Provenance.EXTRACTED) continue;
|
|
7273
|
+
if (e.target === nodeId || byTarget.has(e.target)) continue;
|
|
7274
|
+
const route = e.evidence?.pathTemplate;
|
|
7275
|
+
byTarget.set(
|
|
7276
|
+
e.target,
|
|
7277
|
+
route !== void 0 ? { target: e.target, route } : { target: e.target }
|
|
7278
|
+
);
|
|
7279
|
+
}
|
|
7280
|
+
}
|
|
7281
|
+
return [...byTarget.values()];
|
|
7282
|
+
}
|
|
7283
|
+
function normalizeRoute(s) {
|
|
7284
|
+
return s.replace(/\/+$/, "").toLowerCase();
|
|
7285
|
+
}
|
|
7286
|
+
function routeMatches(declared, incident) {
|
|
7287
|
+
const a = normalizeRoute(declared);
|
|
7288
|
+
const b = normalizeRoute(incident);
|
|
7289
|
+
return a === b || a.startsWith(b) || b.startsWith(a);
|
|
7290
|
+
}
|
|
7291
|
+
function structuralUpstreamPointer(graph, boundaryNode, incidents) {
|
|
7292
|
+
const route = boundaryIncidentRoute(boundaryNode, incidents);
|
|
7293
|
+
const callees = declaredOutboundCallees(graph, boundaryNode);
|
|
7294
|
+
if (callees.length === 0) return null;
|
|
7295
|
+
const narrowed = route !== void 0 ? callees.filter((c) => c.route !== void 0 && routeMatches(c.route, route)) : callees;
|
|
7296
|
+
const chosen = narrowed.length === 1 ? narrowed[0] : callees.length === 1 ? callees[0] : null;
|
|
7297
|
+
if (!chosen) return null;
|
|
7298
|
+
let node = chosen.target;
|
|
7299
|
+
const seen = /* @__PURE__ */ new Set([boundaryNode, node]);
|
|
7300
|
+
for (let depth = 0; depth < ROOT_CAUSE_MAX_DEPTH; depth++) {
|
|
7301
|
+
const next = declaredOutboundCallees(graph, node);
|
|
7302
|
+
if (next.length !== 1 || seen.has(next[0].target)) break;
|
|
7303
|
+
node = next[0].target;
|
|
7304
|
+
seen.add(node);
|
|
7305
|
+
}
|
|
7306
|
+
return route !== void 0 ? { node, route } : { node };
|
|
7307
|
+
}
|
|
7221
7308
|
function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
7222
7309
|
const legacy = tagged.result;
|
|
7223
7310
|
const seedNode = legacy.rootCauseNode;
|
|
@@ -7251,6 +7338,29 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
|
7251
7338
|
confidence: Math.min(legacy.confidence, 0.4),
|
|
7252
7339
|
...lastProv ? { provenance: lastProv } : {}
|
|
7253
7340
|
});
|
|
7341
|
+
} else if (seedCtx && isBoundaryTimeoutSymptom(seedCtx)) {
|
|
7342
|
+
const pointer = structuralUpstreamPointer(graph, seedNode, incidents);
|
|
7343
|
+
const seedName = displayNameOf(seedNode);
|
|
7344
|
+
const routeNote = pointer?.route ? ` serving ${pointer.route}` : "";
|
|
7345
|
+
if (pointer) {
|
|
7346
|
+
const causeName = displayNameOf(pointer.node);
|
|
7347
|
+
candidates.push({
|
|
7348
|
+
node: pointer.node,
|
|
7349
|
+
classification: "primary-failure",
|
|
7350
|
+
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.`,
|
|
7351
|
+
context: nodeContext(graph, pointer.node, incidents, now),
|
|
7352
|
+
confidence: Math.min(legacy.confidence, PROVENANCE_CEILING.EXTRACTED),
|
|
7353
|
+
provenance: import_types8.Provenance.INFERRED
|
|
7354
|
+
});
|
|
7355
|
+
}
|
|
7356
|
+
candidates.push({
|
|
7357
|
+
node: seedNode,
|
|
7358
|
+
classification: "symptom-only",
|
|
7359
|
+
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.`,
|
|
7360
|
+
context: seedCtx,
|
|
7361
|
+
confidence: Math.min(legacy.confidence, 0.3),
|
|
7362
|
+
...lastProv ? { provenance: lastProv } : {}
|
|
7363
|
+
});
|
|
7254
7364
|
} else if (staleChain) {
|
|
7255
7365
|
const culprit = staleChain.culprit;
|
|
7256
7366
|
const culpritName = displayNameOf(culprit);
|