feat(insights): ship deduplicated log bundles to nethesis-insights - #70
Draft
gsanchietti wants to merge 4 commits into
Draft
feat(insights): ship deduplicated log bundles to nethesis-insights#70gsanchietti wants to merge 4 commits into
gsanchietti wants to merge 4 commits into
Conversation
…ctor Log analysis moves off the node and into the nethesis-insights server (https://github.com/nethesis/nethesis-insights). The node now only collects, scrubs, masks and deduplicates one window of journal logs into counted templates, then ships the bundle. It runs no prompt, parses no findings and holds no LLM credential. Removing that credential is the point of the change: the previous design put an OpenAI-compatible API key on every node that enabled the feature, and any prompt or schema change had to land in both a Python edge implementation and a Go server one, which is how the two drift into different definitions of a finding. Per the design spec's cutover section the edge is replaced outright rather than dual-run. The configurable surface shrinks from six parameters to three: active enables insights-collector.timer base_url INSIGHTS_SERVER_URL verify_tls INSIGHTS_VERIFY_TLS, default true Identity is no longer configurable. The collector reads system_id and its secret from the cluster/subscription Redis hash at each fire and sends them as Authorization: Basic, so a configured collector cannot be pointed at another tenant by editing module state, and a subscription registered after the fact starts working with no reconfiguration. With no module secret left, state/secrets.env and its state-include entry are gone. The timer fires every 15 minutes, matching the collector's window. Its randomised delay of up to 2 minutes still resolves to the just-closed window, because compute_window() floors to the previous boundary. Also: - set-anomaly-detector is replaced by set-insights; get-configuration returns an "insights" block carrying subscription_configured, which is what tells the UI why an enabled collector ships nothing - subscription-changed disables the timer and clears the environment on termination, so a terminated node stops firing against an identity that no longer validates - restore-module/95insights re-enables the timer after a restore, since timer enablement is not module state - the gen-1 script, its 1142-line unit test, its robot suite and the LLM stub are deleted; the new suite covers window flooring, the Basic credential, TLS context selection and the identity fallbacks, and asserts that a missing subscription logs field names but never values Assisted-by: Claude Code:claude-opus-5[1m]
gsanchietti
force-pushed
the
anomaly_detector
branch
from
August 7, 2026 12:41
de337c3 to
5a2d628
Compare
urllib only wraps send-phase failures as URLError; a timeout while waiting for the server's response on an already-open connection surfaces as a bare TimeoutError, which crashed main() with a traceback instead of the clean "ship failed" message every other network failure gets. Assisted-by: Claude Code:claude-sonnet-5
9100 is node_exporter's default port, already bound on every NS8 test node. The stub silently lost the bind and the suite's health check ended up talking to node_exporter instead, failing the whole Insights suite on both dn1 and rl1 in CI. Assisted-by: Claude Code:claude-sonnet-5
The dev box is getting torn down soon. Capture the steps to rebuild it back to the state used for this branch's work, so a future agent doesn't have to reconstruct them from session history. Assisted-by: Claude Code:claude-sonnet-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The node stops analysing its own logs.
ns8-lokinow ships deduplicated logbundles to a central analysis server,
nethesis-insights,and does nothing else with them.
This PR originally added an hourly detector that called an OpenAI-compatible
LLM from every node, with a configured API key, and wrote findings back to
the journal. That design does not survive a fleet: it puts a third-party
credential on 2700 machines, gives no cross-node memory, leaves cost
uncontrolled, and forces every prompt or schema change to land in two
implementations — a Python one at the edge and a Go one on the server — which
is how the two drift into different definitions of a finding.
The branch has been rewritten accordingly. The edge is replaced outright, not
dual-run: the LLM call, prompt rendering, findings parsing, webhook and
recall_findings()are gone from the node, and the API key with them.Implements the cutover described in
docs/superpowers/specs/2026-08-05-nethesis-insights-design.md§14.Related issue
None. This is a draft opened ahead of a formal issue.
What the node does now
A
systemdtimer fires aType=oneshotservice every 15 minutes. One run:compute_window()floors to theprevious boundary, so there is no cursor file and no drift).
(module_id, priority)rate digest,a 7-day baseline, and up to
--max-linesprefiltered lines(
PRIORITY < 5 or category="security").scrub.py), masks volatile tokens (masking.py),and deduplicates the result into counted templates — measured at 7.1x
reduction against six hours of real cluster logs.
POSTs the bundle to<base_url>/v1/bundles.Gating, inference, finding identity and storage are all server-side.
Configuration
Three parameters, down from six:
activeinsights-collector.timer)falsebase_urlINSIGHTS_SERVER_URLactiveverify_tlsINSIGHTS_VERIFY_TLStrueverify_tls: falseexists for self-signed test servers. Parsing fails safe:only
0,false,noandoffdisable verification; unset, empty andunrecognised values all verify.
No API key is required or accepted any more. Identity is not configurable:
the collector reads
system_idandauth_tokenfrom thecluster/subscriptionRedis hash at each fire and sends
Authorization: Basic base64(system_id:secret).Two consequences worth stating — a configured collector cannot be pointed at
another tenant by editing module state, and a subscription registered after the
fact starts working with no reconfiguration. The server binds the bundle body's
system_idto the authenticated one and rejects a mismatch with403.Security
state/secrets.envis deleted, alongwith its
state-include.confentry. The subscription credential is read fromRedis per run and never written to disk, never passed through
agent.set_env(which mirrors into
module/<id>/environment), and never logged.present in
cluster/subscriptionand never a value — enough for an operatorto correct the node, not enough to leak a credential into the journal.
sshdlines reach thebundle by design), so every line is flattened to one line and scrubbed before
templating.
SYSLOG_IDENTIFIERbefore the priorityfilter, so its diagnostics cannot feed back into the next window.
--printneeds neither a subscription nor a server URL and ships nothing, soan operator can see exactly what would leave the node before enabling
anything.
Lifecycle
subscription-changed(terminated) disables the timer and clears theenvironment — otherwise a terminated node keeps firing every 15 minutes
against an identity that no longer validates.
restore-module/95insightsre-enables the timer whenINSIGHTS_SERVER_URLcame back, since timer enablement is not module state.
update-module.d/15systemdtears down the never-releasedanomaly-detector.timeron dev nodes that installed an earlier branch build.API changes
get-configurationreturns aninsightsblock in place ofanomaly_detector:subscription_configuredis what tells the UI why an enabled collector isshipping nothing.
set-anomaly-detectoris removed and replaced byset-insights. Nothingconsumed the old action outside this branch, which was never released.
How to test
tests/20__insights.robotruns the same flow against a local stub server(
tests/insights-stub.py), asserting that the bundle carries a realsystem_id, aBasiccredential, and the injected synthetic line.Verification
flooring, the
Basiccredential, TLS context selection in both directions,the identity fallbacks, and the guarantee that a missing subscription logs
field names but never values.
nethesis-insightsserver prototype verified end to end against thiscluster's live Loki: template counts, security-category propagation, and
idempotent re-delivery of the same window.
Known gaps before this leaves draft
verify_tlsis not yet proven in both directions against a real self-signedserver. The flag is not tested until
trueactually rejects.imageroot/pypkg/insights/scrub.pyhas no dedicated unit test. ItsAuthorization-header, Bearer-token, email and long-blob rules are coveredonly incidentally by one assertion in
test_bundle.py, so a regression inthem would pass the suite silently. This is the code most directly
responsible for keeping secrets out of a bundle and deserves its own tests.
tests/20__insights.robothas not run in CI yet.