Repository navigation
feat(notify): adaptive offline threshold and certain node analytics (#2260) - #26
Merged
Merged
Conversation
efiten
force-pushed
the
feat/adaptive-offline
branch
from
October 22, 2026 11:14
000e458 to
7ad251a
Compare
Owner
Author
|
Review feedback addressed (commit
Not changed, with reasons:
|
…awbot#2260) A repeater silent since 18 Oct showed "Active, last relayed 4m ago" with Last Heard 36m, and its offline mail never fired. Every hit after that date was the 1-byte hop 67, shared by nine nodes: the live resolver's affinity tiebreak put it in byNode, and last_relayed folds in the 1-byte prefix bucket on purpose (Kpa-clawbot#662). The store now keeps certainHeard per node: the newest transmission that can only be that node. Its own packets (pubKey / srcPubKey) count, and a flood relay hop counts when its prefix at the packet's path hash size matches exactly one path-capable node. Direct-route paths (hops still to come, Mesh::removeSelfFromPath) and TRACE (SNR bytes) never count. The same rule feeds every load path: Load, loadChunk, scanAndMergeChunk, live ingest and late observations. Liveness reads only that: - /api/nodes and the node detail: last_heard is the newer of last_seen and the certain last heard, no longer a copy of last_seen; - health and bulk-health stats.lastHeard; - LastHeardMap, so the notify offline check; the separate relay input (lastRelayed / relayTimes) is gone; - roles.js getEffectiveHeardMs: the newest known timestamp, without last_relayed; the side pane no longer says "Last relayed". Attribution for analytics (byNode, packet counts, last_relayed, relay counters) is unchanged. Cost: noteCertainRelays 116 ns per observation for 8 hops, 0 allocs; LastHeardMap 0.77 ms for 5,000 nodes (was 3.7 ms), one map lookup each. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…pa-clawbot#2260) Part 2 of Kpa-clawbot#2260, on top of the certain-liveness fix. Adaptive offline threshold (userManagement.notifications.adaptiveOffline, off by default). A watched node's node.offline window is learned from its own certain traffic: certain packets per hour over the week before its last certain packet (ending there, not at now, keeps an outage from dragging the rate down until it reads "back online"), λ the 10th percentile so quiet nights do not alert, window 14/λ hours (silence at that rate has under a one-in-a-million chance), clamped to [floorMinutes, the role's silent window]. Nodes with λ < 1/hour, under a day of history or fewer than minObservers observers keep the fixed window: one observer dropping out must not read as the node going offline. The notifier caches each learned window for 10 minutes and drops unwatched nodes. The Kpa-clawbot#2260 repeater (~840 packets a day) would have been mailed about 30 minutes into its outage instead of never. Node analytics: each timeline bucket carries certain, the part that can only be this node (certainFor: the packet-level form of the certainHeard rule). Availability and the longest silence come from certain buckets only, and the longest silence includes one still running. The chart stacks certain (--accent) and uncertain (--text-muted). Cost: CertainActivity walks a week of packets per watched node, at most once per node per 10 minutes, with the read lock taken per node: 12 ms for 50 busy repeaters with 6,000 packets each (~0.25 ms per lock hold). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
efiten
force-pushed
the
feat/adaptive-offline
branch
from
October 22, 2026 12:32
7ad251a to
b0a37f2
Compare
Owner
Author
|
Review notes addressed (commit Answers to the questions: silence is measured against the newest ingested packet; the zero-run rule covers only runs that recur and are at most 6 h, longer or one-off runs are outages.
|
…lawbot#2260) Retention copies a node into inactive_nodes and nothing removes the row when the node is heard again. markRetired loaded those rows too, so such a node's own prefix counted as a retired namesake and it got no certain relay at any width. markRetired now skips rows whose public_key is also in nodes. On a live instance 838 of the 7877 repeater and room rows in inactive_nodes are also in nodes; repeaters and rooms with a certain relay in the last 72 h went from 1256 to 629 with the rule, 1206 with this fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Part 2 of Kpa-clawbot#2260. Builds on #25 (upstream Kpa-clawbot#2261) (certain liveness): review/merge that first; until then this diff also shows its commit.
Adaptive offline threshold
userManagement.notifications.adaptiveOffline— off by default:The fixed
infraSilentHours(72 h) treats every repeater the same. With this on, a watched node'snode.offlinewindow is learned from its own certain traffic (the Kpa-clawbot#2261 rule):floorMinutes, the role's silent window].Kept on the fixed window: λ < 1/hour, under 24 h of history in memory, or a 10th-percentile hour heard by fewer than
minObserversobservers (one observer dropping out must not read as the node going offline). Silence is measured up to the newest packet in the store, so a stalling feed stops every node's clock. A node stored offline comes back only on evidence from the moment it went offline on. The notifier caches each learned window for 10 minutes and drops unwatched nodes.For the Kpa-clawbot#2260 repeater (~840 packets/day) the mail would have gone out about 30 minutes into the outage, instead of never (or 72 h with Kpa-clawbot#2261 alone).
Certain node analytics
activityTimelinebucket carriescertain: the part that can only be this node (certainFor, the packet-level form of thecertainHeardrule).TimeBucket.Certainisomitempty, so other endpoints are unchanged.--accent) and uncertain (--text-muted) — existing theme variables, so no new customizer entry. Descriptions updated.Performance
CertainActivitywalks a week of packets per watched node, at most once per node per 10 minutes, with the read lock taken per node:Node analytics adds one
certainForper packet of the requested node (a prefix-map lookup, or the cached decoded JSON for its own packets).Tests
adaptiveOfflineAfter: busy repeater hits the floor; 5/hour; quiet nights set the rate; > 10 % empty hours, short history, too few observers → fixed.CertainActivityends at the last certain packet and counts only certain packets — verified red by mutating the window to end at now.certainFor,longestSilence(incl. running silence), config defaults,/api/nodes/{pk}/analyticssplits a shared-prefix bucket.NodeAnalyticsTimeline.series(newtests/unit/test-node-analytics-certain-timeline.js).cmd/servergo test ./...andtest-all.shpass locally, also on a trial merge into a downstream fork.Validation
Server on the freshened e2e fixture: analytics buckets carry
certain(e.g. 89 of 90 packets certain for one repeater). No browser screenshot: Playwright isn't installed on this machine.Later (rule 8)
floorMinutes/minObserversare server config, not theme settings; an admin UI for notification settings would be the place, not the customizer.🤖 Generated with Claude Code