Closes the last ROADMAP §1 item. The replay/replay-graylog CLI backend
already existed and was already safe (a temporary, auto-cleaned SQLite
baseline, never touching live state) — the gap was purely that a human had
to SSH in and run it by hand. Extracts the core logic from cli.py into
run_local_replay()/run_graylog_replay() in replay.py, reused by both the
CLI (now thin wrappers) and a new dashboard panel (Diagnostics -> Replay &
Detector Deltas). Runs as a background job via an in-memory job store,
since the dashboard already runs ThreadingHTTPServer and this session
directly observed live MCP polling take anywhere from 9 minutes to over an
hour — a synchronous request/response was never viable. Deliberately
serialized to one job at a time given this box's GPU/CPU constraints.
Also removed a redundant "Detector Precision" panel that duplicated (with
a weaker, current-poll-cycle-only computation) what the new Detector
Health panel already does correctly with real historical data.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Retention and per-field value capping already ran automatically every poll
cycle, but the dashboard showed only a single "DB size" line — no
visibility into what got deleted, which fields are hitting the
max-values-per-field cap, or whether disk space is reclaimable, without
SSHing in. baseline.py's maintenance() now also reports the specific
(stream, entity, field) rows it trimmed for exceeding the cap, surfaced in
a new Baseline Maintenance dashboard panel alongside size/reclaimable bytes
and rows deleted last cycle. Deliberately no live-vacuum button: VACUUM
needs the service stopped first, so the dashboard only shows when it's
worth doing and points at the existing offline CLI command.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the old Prometheus-only "detector precision" metric, which
recomputed from whatever field_deviations happened to be active in the
current poll cycle cross-referenced against live FeedbackStore rules — a
revoked or expired suppression rule made the signal vanish, and nothing was
tracked between polls. Adds DetectorPrecisionStore, a permanent append-only
log of review outcomes (kept deliberately separate from FeedbackStore's
mutable suppression rules) recorded the moment a human reviews a finding,
plus BaselineStore.detector_readiness() mirroring the existing per-field
profile_readiness() for per-detector learning state, and a plain-language
detector reference. Surfaced in a new Detector Health dashboard panel.
Observability only — nothing here changes scoring or suppression.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Ollama dashboard assessment was built entirely from pre-computed
statistical scores/reasons/counts — a deliberately tested compaction step
stripped all raw message content before it reached the model, presumably to
keep the prompt small for a local, GPU-constrained instance. That raw
content (sample_events/timeline, already truncated to short messages)
already flows through the pipeline; it was just filtered out at the last
step. Now the top-scoring field deviations and incidents each carry a
small, bounded number of real message excerpts, and the prompt explicitly
asks the model to flag suspicious content (injection attempts, unusual
command lines) that pure statistical scoring can't see — purely a
qualitative addition to the narrative note, never feeding back into scoring
or suppression.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Completes the last deferred item from ROADMAP §0: entities recognized as
the same real-world concept across enabled streams (e.g. srcip in both a
firewall and an IIS stream) now share baseline knowledge of entity
presence/age and known relationship-target values, instead of each stream
learning it cold. Implemented additively (synthetic shared_entity:<group>
namespace in the existing baseline.py tables, no schema migration), scoped
to entity presence + relationship known-values only — per-stream
distribution baselines stay untouched. Also fixes a latent alias-map
collision bug found while building this (ALIAS_CANONICAL_BY_FIELD let
"context" silently steal "hostname"/"dns_query" from their own canonical
groups).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Feedback rules now support value/field/entity/stream scope, require
expiry for broad suppressions, and can be revoked individually.
Adds an active-rules panel, detector precision metrics, and diagnostics.