fix(mcp): get_anomaly keeps the events nearest the occurrence and says how many it left out (#5861)

Each source in an anomaly's `around` was cut to its oldest 40 with
nothing in the reply to say so; on a busy host the events nearest the
occurrence were the ones dropped. Each source now keeps the 40 nearest
the occurrence's start, and `around_left_out` counts the rest. Sources
return everything their bounded query read (Docker and the host agent
no longer cut at 40 and 20 themselves), so the count is honest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 15:59:16 -04:00
co-authored by Claude Opus 5.5
parent 91bb199d14
commit 622addc09e
7 changed files with 63 additions and 28 deletions
+6 -3
View File
@@ -163,7 +163,9 @@ async def get_anomaly(anomaly_id: str, occurrences: int = 5) -> dict:
stops; for a Traefik router, its swarm service's containers on any
host), and kind "steward": a stretch Steward itself wasn't recording
(a restart or deploy), around which any reading can be wrong. From 30
minutes before to 30 minutes after; and
minutes before to 30 minutes after. Each kind keeps the 40 events
nearest the occurrence's start; `around_left_out` counts the rest
(0 when nothing was cut). And
`anomalies_elsewhere`, how many anomalies on other hosts overlapped it.
Many at once points at something shared (network, storage, Steward).
- `series`: the newest shown occurrence with time either side (as long
@@ -194,8 +196,8 @@ async def get_anomaly(anomaly_id: str, occurrences: int = 5) -> dict:
shown = []
for span in spans[:limit]:
usual = _usual_at(kind, slots, span.started_at)
near, elsewhere = await events.around(db, place, a.id, span.started_at - AROUND,
(span.ended_at or now) + AROUND)
near, elsewhere, left_out = await events.around(db, place, a.id, span.started_at - AROUND,
(span.ended_at or now) + AROUND, span.started_at)
shown.append({
"started_at": iso(span.started_at), "ended_at": iso(span.ended_at),
"ongoing": span.ended_at is None,
@@ -206,6 +208,7 @@ async def get_anomaly(anomaly_id: str, occurrences: int = 5) -> dict:
"flag_above": rnd(flag_above(usual.usual_hold, floor), 3) if floor is not None else None}
if usual else None),
"around": near,
"around_left_out": left_out,
"anomalies_elsewhere": elsewhere,
})
series = None