@neat.is/core 0.9.3-dev.20260823 → 0.9.3
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-WJGZYEUG.js → chunk-2D4Y5QHE.js} +2 -2
- package/dist/{chunk-LRYPKC3B.js → chunk-EDE4XP2M.js} +232 -28
- package/dist/chunk-EDE4XP2M.js.map +1 -0
- package/dist/cli.cjs +242 -28
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +15 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +230 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +230 -27
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +230 -27
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-LRYPKC3B.js.map +0 -1
- /package/dist/{chunk-WJGZYEUG.js.map → chunk-2D4Y5QHE.js.map} +0 -0
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
startStalenessLoop,
|
|
21
21
|
touchLastSeen,
|
|
22
22
|
writeAtomically
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-EDE4XP2M.js";
|
|
24
24
|
import {
|
|
25
25
|
assertBindAuthority,
|
|
26
26
|
buildOtelReceiver,
|
|
@@ -891,4 +891,4 @@ export {
|
|
|
891
891
|
resolveHost,
|
|
892
892
|
startDaemon
|
|
893
893
|
};
|
|
894
|
-
//# sourceMappingURL=chunk-
|
|
894
|
+
//# sourceMappingURL=chunk-2D4Y5QHE.js.map
|
|
@@ -5125,15 +5125,36 @@ function startStalenessLoop(graph, options = {}) {
|
|
|
5125
5125
|
clearInterval(interval);
|
|
5126
5126
|
};
|
|
5127
5127
|
}
|
|
5128
|
-
|
|
5128
|
+
var INCIDENT_READ_MAX_BYTES = 32 * 1024 * 1024;
|
|
5129
|
+
var INCIDENT_READ_MAX_EVENTS = 5e3;
|
|
5130
|
+
async function readErrorFileTail(errorsPath, maxBytes) {
|
|
5131
|
+
const handle = await fs7.open(errorsPath, "r");
|
|
5129
5132
|
try {
|
|
5130
|
-
const
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
+
const { size } = await handle.stat();
|
|
5134
|
+
if (size <= maxBytes) {
|
|
5135
|
+
return (await handle.readFile()).toString("utf8");
|
|
5136
|
+
}
|
|
5137
|
+
const buf = Buffer.alloc(maxBytes);
|
|
5138
|
+
await handle.read(buf, 0, maxBytes, size - maxBytes);
|
|
5139
|
+
const raw = buf.toString("utf8");
|
|
5140
|
+
const firstNewline = raw.indexOf("\n");
|
|
5141
|
+
return firstNewline === -1 ? "" : raw.slice(firstNewline + 1);
|
|
5142
|
+
} finally {
|
|
5143
|
+
await handle.close();
|
|
5144
|
+
}
|
|
5145
|
+
}
|
|
5146
|
+
async function readErrorEvents(errorsPath, opts) {
|
|
5147
|
+
let raw;
|
|
5148
|
+
try {
|
|
5149
|
+
raw = await readErrorFileTail(errorsPath, INCIDENT_READ_MAX_BYTES);
|
|
5133
5150
|
} catch (err) {
|
|
5134
5151
|
if (err.code === "ENOENT") return [];
|
|
5135
5152
|
throw err;
|
|
5136
5153
|
}
|
|
5154
|
+
const events = raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
|
|
5155
|
+
const deduped = dedupeIncidents(events);
|
|
5156
|
+
const cap = opts?.limit !== void 0 && opts.limit > 0 ? Math.min(opts.limit, INCIDENT_READ_MAX_EVENTS) : INCIDENT_READ_MAX_EVENTS;
|
|
5157
|
+
return deduped.length > cap ? deduped.slice(deduped.length - cap) : deduped;
|
|
5137
5158
|
}
|
|
5138
5159
|
function isSynthesizedHttpIncident(ev) {
|
|
5139
5160
|
if (ev.exceptionType || ev.exceptionStacktrace) return false;
|
|
@@ -5906,6 +5927,47 @@ function classifyNode(ctx) {
|
|
|
5906
5927
|
function isVictimSeed(ctx) {
|
|
5907
5928
|
return ctx.errorsFromCallers > 0 && (ctx.stale || isSaturated(ctx)) && ctx.errorsEmittedHere <= ctx.errorsFromCallers;
|
|
5908
5929
|
}
|
|
5930
|
+
var OUTBOUND_CONNECTION_FAILURE_PATTERNS = [
|
|
5931
|
+
"name resolution",
|
|
5932
|
+
"resolve host",
|
|
5933
|
+
"getaddrinfo",
|
|
5934
|
+
"enotfound",
|
|
5935
|
+
"connection refused",
|
|
5936
|
+
"econnrefused",
|
|
5937
|
+
"connection reset",
|
|
5938
|
+
"econnreset",
|
|
5939
|
+
"able to connect",
|
|
5940
|
+
"failed to connect",
|
|
5941
|
+
"cannot connect",
|
|
5942
|
+
"could not connect",
|
|
5943
|
+
"unable to connect",
|
|
5944
|
+
"connection timed out",
|
|
5945
|
+
"etimedout",
|
|
5946
|
+
"no route to host",
|
|
5947
|
+
"host unreachable",
|
|
5948
|
+
"network is unreachable",
|
|
5949
|
+
"connection closed"
|
|
5950
|
+
];
|
|
5951
|
+
function incidentTextIndicatesOutboundFailure(ev) {
|
|
5952
|
+
const haystack = [ev.errorMessage, ev.errorType, ev.exceptionType, ev.exceptionStacktrace].filter((s) => typeof s === "string").join(" ").toLowerCase();
|
|
5953
|
+
return OUTBOUND_CONNECTION_FAILURE_PATTERNS.some((p) => haystack.includes(p));
|
|
5954
|
+
}
|
|
5955
|
+
function hasFailingOutbound(graph, nodeId, seedSource, incidents) {
|
|
5956
|
+
for (const n of nodeScope(graph, nodeId)) {
|
|
5957
|
+
if (!graph.hasNode(n)) continue;
|
|
5958
|
+
for (const edgeId of graph.outboundEdges(n)) {
|
|
5959
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
5960
|
+
if (e.type === EdgeType6.CONTAINS) continue;
|
|
5961
|
+
if ((e.signal?.errorCount ?? 0) > 0) return true;
|
|
5962
|
+
}
|
|
5963
|
+
}
|
|
5964
|
+
if (seedSource === "incident" && incidents) {
|
|
5965
|
+
for (const ev of incidents) {
|
|
5966
|
+
if (incidentMatchesNode(ev, nodeId) && incidentTextIndicatesOutboundFailure(ev)) return true;
|
|
5967
|
+
}
|
|
5968
|
+
}
|
|
5969
|
+
return false;
|
|
5970
|
+
}
|
|
5909
5971
|
function grainOf(graph, nodeId) {
|
|
5910
5972
|
if (!graph.hasNode(nodeId)) return "unknown";
|
|
5911
5973
|
const t = graph.getNodeAttributes(nodeId).type;
|
|
@@ -6069,7 +6131,8 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
|
6069
6131
|
const candidates = [];
|
|
6070
6132
|
const deadEndOnSymptom = tagged.source === "incident" && seedNode === errorNodeId && legacy.traversalPath.length === 1;
|
|
6071
6133
|
const staleChain = deadEndOnSymptom && !(seedCtx && isVictimSeed(seedCtx)) ? followStaleCallChain(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH) : null;
|
|
6072
|
-
|
|
6134
|
+
const seedFailsOutbound = seedCtx !== null && hasFailingOutbound(graph, seedNode, tagged.source, incidents);
|
|
6135
|
+
if (seedCtx && isVictimSeed(seedCtx) && !seedFailsOutbound) {
|
|
6073
6136
|
const origin = findLoadOrigin(graph, errorNodeId, incidents, now);
|
|
6074
6137
|
const staleNote = seedCtx.stale ? "; it has gone STALE under load" : "";
|
|
6075
6138
|
const satNote = isSaturated(seedCtx) ? `; its inbound p95 ${Math.round(seedCtx.latencyP95Ms)}ms is saturated` : "";
|
|
@@ -14939,6 +15002,7 @@ import {
|
|
|
14939
15002
|
NodeType as NodeType30,
|
|
14940
15003
|
parseEdgeId,
|
|
14941
15004
|
parseFileId,
|
|
15005
|
+
parseSymbolId as parseSymbolId2,
|
|
14942
15006
|
Provenance as Provenance24,
|
|
14943
15007
|
serviceId as serviceId13
|
|
14944
15008
|
} from "@neat.is/types";
|
|
@@ -15195,6 +15259,120 @@ function detectColumnDrift(node) {
|
|
|
15195
15259
|
}
|
|
15196
15260
|
return out;
|
|
15197
15261
|
}
|
|
15262
|
+
var SYMBOL_MISMATCH_PATTERNS = [
|
|
15263
|
+
{
|
|
15264
|
+
// "'ListProductsResponse' object has no attribute 'products_list'" and kin.
|
|
15265
|
+
kind: "missing-attribute",
|
|
15266
|
+
patterns: [/\bhas no attribute\b[\s:=]*['"`]?([A-Za-z_$][\w$]*)['"`]?/i]
|
|
15267
|
+
},
|
|
15268
|
+
{
|
|
15269
|
+
// "object has no field 'X'", "no such field X", "unknown field X".
|
|
15270
|
+
kind: "missing-field",
|
|
15271
|
+
patterns: [
|
|
15272
|
+
/\b(?:has no field|no such field|unknown field)\b(?:\s+named)?[\s:=]*['"`]?([A-Za-z_$][\w$.]*)['"`]?/i
|
|
15273
|
+
]
|
|
15274
|
+
},
|
|
15275
|
+
{
|
|
15276
|
+
// "has no property X", "no property named X".
|
|
15277
|
+
kind: "missing-property",
|
|
15278
|
+
patterns: [
|
|
15279
|
+
/\b(?:has no property|no property named)\b[\s:=]*['"`]?([A-Za-z_$][\w$]*)['"`]?/i
|
|
15280
|
+
]
|
|
15281
|
+
},
|
|
15282
|
+
{
|
|
15283
|
+
// "no such column: X", "unknown column 'X'", "column X does not exist".
|
|
15284
|
+
kind: "missing-column",
|
|
15285
|
+
patterns: [
|
|
15286
|
+
/\bno such column\b[\s:=]*['"`]?([\w$.]+)['"`]?/i,
|
|
15287
|
+
/\bunknown column\b[\s:=]*['"`]?([\w$.]+)['"`]?/i,
|
|
15288
|
+
/\bcolumn\b[\s:=]*['"`]?([\w$.]+)['"`]?\s+does not exist/i
|
|
15289
|
+
]
|
|
15290
|
+
},
|
|
15291
|
+
{
|
|
15292
|
+
// "undefined method `foo' for X" — kept to the unambiguous form so a generic
|
|
15293
|
+
// "method not found" (an unimplemented RPC — an edge/route gap, not a symbol
|
|
15294
|
+
// mismatch) does not get miscategorised here.
|
|
15295
|
+
kind: "undefined-method",
|
|
15296
|
+
patterns: [/\bundefined method\b\s*[`'"]?([\w$?!]+)/i]
|
|
15297
|
+
}
|
|
15298
|
+
];
|
|
15299
|
+
function classifySymbolMismatch(message) {
|
|
15300
|
+
for (const entry of SYMBOL_MISMATCH_PATTERNS) {
|
|
15301
|
+
for (const re of entry.patterns) {
|
|
15302
|
+
const m = re.exec(message);
|
|
15303
|
+
if (m) {
|
|
15304
|
+
const captured = m[1];
|
|
15305
|
+
return captured ? { kind: entry.kind, symbol: captured } : { kind: entry.kind };
|
|
15306
|
+
}
|
|
15307
|
+
}
|
|
15308
|
+
}
|
|
15309
|
+
return null;
|
|
15310
|
+
}
|
|
15311
|
+
function symbolLocus(graph, ev) {
|
|
15312
|
+
const attrs = ev.attributes ?? {};
|
|
15313
|
+
const filepath = codeFilepathOf(attrs);
|
|
15314
|
+
const lineno = codeLinenoOf(attrs);
|
|
15315
|
+
const location = filepath ? `${filepath}${lineno !== void 0 ? `:${lineno}` : ""}` : void 0;
|
|
15316
|
+
const affected = ev.affectedNode;
|
|
15317
|
+
const affectedInGraph = affected.length > 0 && graph.hasNode(affected);
|
|
15318
|
+
const affectedIsCode = affectedInGraph && (parseSymbolId2(affected) !== null || parseFileId(affected) !== null);
|
|
15319
|
+
if (affectedIsCode) return { node: affected, ...location ? { location } : {} };
|
|
15320
|
+
if (!location) return null;
|
|
15321
|
+
if (affectedInGraph) return { node: affected, location };
|
|
15322
|
+
const svc = serviceId13(ev.service);
|
|
15323
|
+
if (graph.hasNode(svc)) return { node: svc, location };
|
|
15324
|
+
return null;
|
|
15325
|
+
}
|
|
15326
|
+
var SYMBOL_MISMATCH_CONFIDENCE = 0.6;
|
|
15327
|
+
function detectSymbolMismatches(graph, incidents) {
|
|
15328
|
+
const groups = /* @__PURE__ */ new Map();
|
|
15329
|
+
for (const ev of incidents) {
|
|
15330
|
+
const classified = classifySymbolMismatch(ev.errorMessage);
|
|
15331
|
+
if (!classified) continue;
|
|
15332
|
+
const locus = symbolLocus(graph, ev);
|
|
15333
|
+
if (!locus) continue;
|
|
15334
|
+
const key = `${locus.node}|${classified.kind}|${classified.symbol ?? ""}`;
|
|
15335
|
+
const existing = groups.get(key);
|
|
15336
|
+
if (!existing) {
|
|
15337
|
+
groups.set(key, {
|
|
15338
|
+
node: locus.node,
|
|
15339
|
+
kind: classified.kind,
|
|
15340
|
+
...classified.symbol ? { symbol: classified.symbol } : {},
|
|
15341
|
+
...locus.location ? { location: locus.location } : {},
|
|
15342
|
+
latest: ev,
|
|
15343
|
+
count: 1
|
|
15344
|
+
});
|
|
15345
|
+
} else {
|
|
15346
|
+
existing.count += 1;
|
|
15347
|
+
if (ev.timestamp.localeCompare(existing.latest.timestamp) > 0) {
|
|
15348
|
+
existing.latest = ev;
|
|
15349
|
+
if (locus.location) existing.location = locus.location;
|
|
15350
|
+
}
|
|
15351
|
+
}
|
|
15352
|
+
}
|
|
15353
|
+
const out = [];
|
|
15354
|
+
for (const g of groups.values()) {
|
|
15355
|
+
const member = g.symbol ? `\`${g.symbol}\`` : "a member";
|
|
15356
|
+
const where = g.location ? ` at ${g.location}` : "";
|
|
15357
|
+
const times = g.count > 1 ? ` (${g.count} recorded incidents)` : " (1 recorded incident)";
|
|
15358
|
+
out.push({
|
|
15359
|
+
type: "observed-symbol-mismatch",
|
|
15360
|
+
source: g.node,
|
|
15361
|
+
target: g.node,
|
|
15362
|
+
mismatchKind: g.kind,
|
|
15363
|
+
...g.symbol ? { symbol: g.symbol } : {},
|
|
15364
|
+
...g.location ? { location: g.location } : {},
|
|
15365
|
+
provenance: Provenance24.INFERRED,
|
|
15366
|
+
incidentId: g.latest.id,
|
|
15367
|
+
errorMessage: g.latest.errorMessage,
|
|
15368
|
+
incidentCount: g.count,
|
|
15369
|
+
confidence: SYMBOL_MISMATCH_CONFIDENCE,
|
|
15370
|
+
reason: `Code${where} declares access to ${member} the runtime object does not have \u2014 ${g.latest.service} raised "${g.latest.errorMessage}"${times}. The declared access (EXTRACTED) and the runtime shape (OBSERVED) disagree at symbol grain.`,
|
|
15371
|
+
recommendation: "Reconcile the declared access with the runtime shape: a field, attribute, method, or column was renamed, removed, or never existed on the object this code reaches. Update the code to the current shape, or restore the member."
|
|
15372
|
+
});
|
|
15373
|
+
}
|
|
15374
|
+
return out;
|
|
15375
|
+
}
|
|
15198
15376
|
function involvesNode(d, nodeId) {
|
|
15199
15377
|
return d.source === nodeId || d.target === nodeId;
|
|
15200
15378
|
}
|
|
@@ -15280,6 +15458,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
15280
15458
|
for (const d of detectColumnDrift(n)) all.push(d);
|
|
15281
15459
|
}
|
|
15282
15460
|
});
|
|
15461
|
+
if (opts.incidents && opts.incidents.length > 0) {
|
|
15462
|
+
for (const d of detectSymbolMismatches(graph, opts.incidents)) all.push(d);
|
|
15463
|
+
}
|
|
15283
15464
|
const reconciled = suppressHostMismatchHalves(all);
|
|
15284
15465
|
const dampened = dampenDeadCodeProbes(graph, buckets2, reconciled);
|
|
15285
15466
|
let filtered = dampened;
|
|
@@ -15300,7 +15481,12 @@ function computeDivergences(graph, opts = {}) {
|
|
|
15300
15481
|
"missing-observed": 1,
|
|
15301
15482
|
"version-mismatch": 2,
|
|
15302
15483
|
"host-mismatch": 3,
|
|
15303
|
-
"compat-violation": 4
|
|
15484
|
+
"compat-violation": 4,
|
|
15485
|
+
// Symbol/field-grain (ADR-215) rides the confidence sort like every other
|
|
15486
|
+
// type; this only breaks a confidence tie, and it orders last so a same-
|
|
15487
|
+
// confidence edge finding leads. In practice it carries the INFERRED grade
|
|
15488
|
+
// (0.6), so it sits below the high-confidence edge divergences already.
|
|
15489
|
+
"observed-symbol-mismatch": 5
|
|
15304
15490
|
};
|
|
15305
15491
|
filtered.sort((a, b) => {
|
|
15306
15492
|
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
@@ -15311,7 +15497,10 @@ function computeDivergences(graph, opts = {}) {
|
|
|
15311
15497
|
if (a.target !== b.target) return a.target.localeCompare(b.target);
|
|
15312
15498
|
const ac = "column" in a && a.column ? a.column : "";
|
|
15313
15499
|
const bc = "column" in b && b.column ? b.column : "";
|
|
15314
|
-
return ac.localeCompare(bc);
|
|
15500
|
+
if (ac !== bc) return ac.localeCompare(bc);
|
|
15501
|
+
const asym = "symbol" in a && a.symbol ? a.symbol : "";
|
|
15502
|
+
const bsym = "symbol" in b && b.symbol ? b.symbol : "";
|
|
15503
|
+
return asym.localeCompare(bsym);
|
|
15315
15504
|
});
|
|
15316
15505
|
return DivergenceResultSchema.parse({
|
|
15317
15506
|
divergences: filtered,
|
|
@@ -16754,10 +16943,15 @@ function divergenceLine(d) {
|
|
|
16754
16943
|
if ((d.type === "missing-observed" || d.type === "missing-extracted") && d.column) {
|
|
16755
16944
|
return `[${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 ${d.reason}`;
|
|
16756
16945
|
}
|
|
16946
|
+
if (d.type === "observed-symbol-mismatch") {
|
|
16947
|
+
const at = d.location ? ` at ${d.location}` : "";
|
|
16948
|
+
const member = d.symbol ? ` ${d.symbol}` : "";
|
|
16949
|
+
return `[${d.type}] ${d.source}${member}${at} \u2014 ${d.reason}`;
|
|
16950
|
+
}
|
|
16757
16951
|
return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
|
|
16758
16952
|
}
|
|
16759
|
-
function buildDivergenceSection(graph, node) {
|
|
16760
|
-
const result = computeDivergences(graph, { node });
|
|
16953
|
+
function buildDivergenceSection(graph, node, incidents) {
|
|
16954
|
+
const result = computeDivergences(graph, { node, ...incidents ? { incidents } : {} });
|
|
16761
16955
|
if (result.totalAffected === 0) return null;
|
|
16762
16956
|
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16763
16957
|
text: divergenceLine(d),
|
|
@@ -16770,8 +16964,8 @@ function buildDivergenceSection(graph, node) {
|
|
|
16770
16964
|
facts
|
|
16771
16965
|
};
|
|
16772
16966
|
}
|
|
16773
|
-
function buildGlobalDivergenceSection(graph) {
|
|
16774
|
-
const result = computeDivergences(graph);
|
|
16967
|
+
function buildGlobalDivergenceSection(graph, incidents) {
|
|
16968
|
+
const result = computeDivergences(graph, incidents ? { incidents } : {});
|
|
16775
16969
|
if (result.totalAffected === 0) {
|
|
16776
16970
|
return {
|
|
16777
16971
|
heading: "Divergences (EXTRACTED vs OBSERVED)",
|
|
@@ -16869,7 +17063,7 @@ function buildOverviewSections(graph, incidents) {
|
|
|
16869
17063
|
}))
|
|
16870
17064
|
});
|
|
16871
17065
|
}
|
|
16872
|
-
const div = computeDivergences(graph);
|
|
17066
|
+
const div = computeDivergences(graph, incidents ? { incidents } : {});
|
|
16873
17067
|
sections.push({
|
|
16874
17068
|
heading: "Divergences",
|
|
16875
17069
|
facts: [
|
|
@@ -16883,7 +17077,7 @@ function buildOverviewSections(graph, incidents) {
|
|
|
16883
17077
|
function buildGlobalSections(intent, graph, incidents) {
|
|
16884
17078
|
switch (intent) {
|
|
16885
17079
|
case "divergence":
|
|
16886
|
-
return [buildGlobalDivergenceSection(graph)];
|
|
17080
|
+
return [buildGlobalDivergenceSection(graph, incidents)];
|
|
16887
17081
|
case "incidents":
|
|
16888
17082
|
return [buildGlobalIncidentsSection(incidents)];
|
|
16889
17083
|
case "overview":
|
|
@@ -16914,7 +17108,7 @@ function buildSection(kind, graph, node, incidents, now) {
|
|
|
16914
17108
|
case "incidents":
|
|
16915
17109
|
return buildIncidentsSection(node, incidents);
|
|
16916
17110
|
case "divergence":
|
|
16917
|
-
return buildDivergenceSection(graph, node);
|
|
17111
|
+
return buildDivergenceSection(graph, node, incidents);
|
|
16918
17112
|
}
|
|
16919
17113
|
}
|
|
16920
17114
|
function summarizeGlobal(intent, sections) {
|
|
@@ -20536,6 +20730,8 @@ async function deprovisionConnector(entry, env = process.env, fetchImpl) {
|
|
|
20536
20730
|
}
|
|
20537
20731
|
|
|
20538
20732
|
// src/api.ts
|
|
20733
|
+
var INCIDENT_LIST_DEFAULT_LIMIT = 50;
|
|
20734
|
+
var INCIDENT_LIST_MAX_LIMIT = 200;
|
|
20539
20735
|
function serializeGraph(graph) {
|
|
20540
20736
|
const nodes = [];
|
|
20541
20737
|
graph.forEachNode((_id, attrs) => {
|
|
@@ -20715,10 +20911,13 @@ function registerRoutes(scope, ctx) {
|
|
|
20715
20911
|
}
|
|
20716
20912
|
minConfidence = n;
|
|
20717
20913
|
}
|
|
20914
|
+
const epath = errorsPathFor(proj);
|
|
20915
|
+
const incidents = epath ? await readErrorEvents(epath) : [];
|
|
20718
20916
|
return computeDivergences(proj.graph, {
|
|
20719
20917
|
...typeFilter ? { type: typeFilter } : {},
|
|
20720
20918
|
...minConfidence !== void 0 ? { minConfidence } : {},
|
|
20721
|
-
...req.query.node ? { node: req.query.node } : {}
|
|
20919
|
+
...req.query.node ? { node: req.query.node } : {},
|
|
20920
|
+
incidents
|
|
20722
20921
|
});
|
|
20723
20922
|
});
|
|
20724
20923
|
scope.get("/incidents", async (req, reply) => {
|
|
@@ -20728,10 +20927,11 @@ function registerRoutes(scope, ctx) {
|
|
|
20728
20927
|
if (!epath) return { count: 0, total: 0, events: [] };
|
|
20729
20928
|
const events = await readErrorEvents(epath);
|
|
20730
20929
|
const total = events.length;
|
|
20731
|
-
const limit = req.query.limit ? Number(req.query.limit) :
|
|
20732
|
-
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit,
|
|
20930
|
+
const limit = req.query.limit ? Number(req.query.limit) : INCIDENT_LIST_DEFAULT_LIMIT;
|
|
20931
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, INCIDENT_LIST_MAX_LIMIT) : INCIDENT_LIST_DEFAULT_LIMIT;
|
|
20733
20932
|
const sliced = events.slice(0, safeLimit);
|
|
20734
|
-
|
|
20933
|
+
const omitted = total - sliced.length;
|
|
20934
|
+
return { count: sliced.length, total, events: sliced, ...omitted > 0 ? { omitted } : {} };
|
|
20735
20935
|
});
|
|
20736
20936
|
scope.get("/stale-events", async (req, reply) => {
|
|
20737
20937
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
@@ -20840,16 +21040,20 @@ function registerRoutes(scope, ctx) {
|
|
|
20840
21040
|
const filtered = events.filter(
|
|
20841
21041
|
(e) => e.affectedNode === nodeId || e.service === nodeId.replace(/^service:/, "")
|
|
20842
21042
|
);
|
|
20843
|
-
|
|
21043
|
+
const total = filtered.length;
|
|
21044
|
+
const limit = req.query.limit ? Number(req.query.limit) : INCIDENT_LIST_DEFAULT_LIMIT;
|
|
21045
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, INCIDENT_LIST_MAX_LIMIT) : INCIDENT_LIST_DEFAULT_LIMIT;
|
|
21046
|
+
const recent = [...filtered].sort((a, b) => (b.timestamp ?? "").localeCompare(a.timestamp ?? "")).slice(0, safeLimit);
|
|
21047
|
+
const omitted = total - recent.length;
|
|
21048
|
+
return {
|
|
21049
|
+
count: recent.length,
|
|
21050
|
+
total,
|
|
21051
|
+
events: recent,
|
|
21052
|
+
...omitted > 0 ? { omitted } : {}
|
|
21053
|
+
};
|
|
20844
21054
|
};
|
|
20845
|
-
scope.get(
|
|
20846
|
-
|
|
20847
|
-
incidentHistoryHandler
|
|
20848
|
-
);
|
|
20849
|
-
scope.get(
|
|
20850
|
-
"/graph/incident-history/:nodeId",
|
|
20851
|
-
incidentHistoryHandler
|
|
20852
|
-
);
|
|
21055
|
+
scope.get("/incidents/:nodeId", incidentHistoryHandler);
|
|
21056
|
+
scope.get("/graph/incident-history/:nodeId", incidentHistoryHandler);
|
|
20853
21057
|
scope.get("/graph/root-cause/:nodeId", async (req, reply) => {
|
|
20854
21058
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
20855
21059
|
if (!proj) return;
|
|
@@ -21491,4 +21695,4 @@ export {
|
|
|
21491
21695
|
deprovisionConnector,
|
|
21492
21696
|
buildApi
|
|
21493
21697
|
};
|
|
21494
|
-
//# sourceMappingURL=chunk-
|
|
21698
|
+
//# sourceMappingURL=chunk-EDE4XP2M.js.map
|