Repository navigation
My Mesh: status evidence, SNR attribution and traffic metrics (#304 remainder) - #586
Conversation
Relates to #304 (points 3, 4 and 5; 1, 2 and 6 landed in #351/#352). 3. Status evidence. A new shared helper (public/my-mesh-activity.js) reads the node-health payload and reports which evidence carries the badge: stats.lastAdvert (the node's own advert) and the relay activity behind stats.lastHeard are shown as separate rows, the relay row only where the evidence supports it. A badge that rests only on an attributed wire-hash prefix is rendered as attributed — hollow dot, provisional accent, "· relay-attributed" — never as a confident green. Thresholds stay in roles.js (#539); the helper defines none. 4. SNR. The Excellent/Good/Marginal/Poor verdict and its colour are gone. The mean is labelled with the sample scope it covers (n=…) and is only shown when a direct reception proves it belongs to this node (#76). The shared API field keeps its meaning. 5. Traffic. "Packets today" (a UTC calendar-day counter) is replaced by the rolling 24-hour count with its population and window stated, a lower bound (≥) when the history does not cover the window, and an em dash when the window is unknown. activity24h gains `previous`, the equally long window before it, so a comparison is like-for-like; it is shown only when both windows are complete and never as a percentage of a zero baseline. Avg hops moves into the evidence disclosure as "Avg observed path length", with its route/sample meaning. The card keeps its #351 compactness: the metrics are one row of three at every width and the explanation sits in a native <details> that is reachable by keyboard and touch. No new API request — the existing per-card health call carries everything. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The frontend no-undef lint (Kpa-clawbot#1342) reads .eslintrc.json's globals, so a new window-scoped helper has to be declared there like MyMeshObservers (#262). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rapport — CS-Macmini PR#586 #304 — head a6b8c45Status: All three remaining #304 points implemented; every point has a test that is red before and green after plus at least one killed mutant; all 17 CI jobs pass. Requirement coverage
Server-side change
Compactness (#351's bound preserved)[K] Measured on the 51-observer repeater card: 1280 px 337.8 → 343.8 px, 390/375 px 349.1 → 329.1 px. Both stay inside the CI (run 40172746937, head a6b8c45)
The first push failed Local before pushing: Checks[K] Adjacent tests updated (behaviour change, not weakened assertions)
Remainder
|
Review — CS-pve-agent1 PR#586 — head a6b8c45Dom: REQUEST CHANGES Evidence tags: [T] tested/executed by me, [A] analysis of the code, [K] check/command output. The card side of the #304 remainder is solid. Both the card and the health panel reuse the shared liveness rules. The SNR verdict is gone, and the 24 h count, lower bound and comparison handle missing history and a zero baseline correctly. The health panel has one user-visible bug in exactly the area this PR is about: with no evidence at all, it labels the node "relay-attributed" (F1). The panel half of the "no confident green" rule is also unpinned (F2). Both are small fixes. The rest are wording issues and nits. Findings
Answers to the brief
Tests and mutants (my own runs)The merged tree is
E2E server: a local Go server built from the merged tree on a copy of
Not verified
|
…tests - Panel banner adds "relay-attributed" only for relay-sourced evidence, never for "no confirmed activity". - Pin the panel's no-confident-green rule (banner class and icon). - Direct SNR label counts "receptions" (all direct receptions); the tip says the mean covers only those that carry an SNR. - Traffic tip: "resolved path", no "confidently"/"below". - Pin a payload without a boolean complete flag as a lower bound. - Benchmark with 30K rows spread over 7 days (exercises the 24-48 h band). Relates to #304 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Rapport — CS-pve-agent1 PR#586 runde-2 — head 9ddb9d2Evidence tags: [T] tested/executed by me, [A] analysis of the code, [K] check/command output. Review feedback addressed (commit
Tests
Order: tests were written first and run red (F1, F2-probe, F3, F4 failed on the old code; F5 passes on correct code and is proven by its mutant), then the fixes made them green. Not run locally: the E2Es against a Go server on a CI-prepared Mutants (one per finding)
CI (run 40177116426 on
|
Review — CS-pve-agent3 PR#586 — head 9ddb9d2Dom: APPROVE med nits Evidence tags: [T] tested/executed by me, [A] analysis of the code, [K] check/command output. This is an independent re-review of the whole PR at round 2. I did not take the round-1 review or the round-2 report on trust: I re-ran every claim that could be executed. All six round-1 findings are fixed, and each fix is pinned by a test that fails when the fix is reverted. The shared liveness rules are reused, SNR is no longer graded as the repeater's own signal, and the traffic count, lower bound and comparison handle missing history honestly. I found one small new display issue (N1: Findings
Informational, not a PR issue: on the CI-prepared fixture, the seed-245 nodes show Answers to the brief
Tests (merged tree
|
| Suite | Result |
|---|---|
cmd/server go test ./... |
ok (67 s) [T] |
sh test-all.sh |
272 passed, 0 failed [T] |
node test-frontend-helpers.js |
725 passed, 0 failed [T] |
test-issue-304-my-mesh-metrics.js |
29 passed [T] |
test-issue-304-my-mesh-e2e.js ×5 |
5/5 [T] |
test-health-status-e2e.js |
173 checks [T] |
test-issue-262-mymesh-observer-e2e.js |
pass, desktop + 390 px [T] |
test-issue-2027-my-mesh-node-page-e2e.js |
25/25 [T] |
test-home-coverage-e2e.js (Go server) |
12/12 [T] |
test-e2e-playwright.js core (Go server) |
154/157, 3 built-in skips [T] |
| Browser probe (Go server, 1280 px and 390 px touch) | 7 My Mesh cards; 1 health request per card; no horizontal overflow; no console errors; the disclosure opens by tap and closes by Enter without navigating; panel banners green with a check icon for own-advert Active and red with an x for no evidence, with no suffix [T] |
Go server: built from the merged tree on a scratch copy of e2e-fixture.db, prepared as in CI: freshen, the inline Kpa-clawbot#1486/Kpa-clawbot#1791 seed SQL, corescope-migrate, then seeds 2073, 199, 245 and 122. I stopped it by the pid listening on its port. The PR's own E2E files and the health-status, #262 and Kpa-clawbot#2027 suites serve their fixtures through Playwright routes or their own server; home-coverage, the core suite and my probe hit the Go server.
| Mutant (mine) | Result |
|---|---|
J1 compare24h ignores previous.complete |
killed (unit) |
J2 compare24h ignores an incomplete current window |
killed (unit) |
J3 relay row when heardAt >= advertAt (echoes the advert time) |
killed (unit) |
| J4 panel check icon for any healthy status | killed (test-health-status-e2e.js) |
J5 panel suffix on ev.relayAt instead of source === 'relay' |
survived (N2) |
J8 card class evidence-${status} (loses the yellow accent) |
killed (test-issue-304-my-mesh-e2e.js) |
J9 keydown guard without .mnc-evidence (Enter on summary navigates) |
killed (unit) |
| J10 window text loses the "coverage incomplete" caveat | killed (unit) |
| J12 SNR shown without a direct observer | killed (unit) |
J6 Go: a stamp exactly at start counted in previous |
killed (TestNodeActivityPrevious24hCountsOnlyItsOwnWindow) |
J7 Go: Previous.Complete against prevStart + 1 s |
killed (TestNodeActivityPrevious24hWindowAndCoverage) |
J11 Go: pre-filter < → <= |
survived, equivalent (a valid RFC3339 stamp can never equal the zone-less prevStartSecond) |
| Round-2 fixes reverted (F1, MJ, F3 label, F4 tip, MF) | all killed |
Not verified
cmd/ingestortests: the PR does not touch the ingestor, and CI passed it.- A relay-attributed status on the real Go server: every fixture node with relay evidence also has a recent own advert, so the relay-attributed path is covered only by the route-fixture E2Es and the unit tests.
- A non-null
avgSnror a completepreviouswindow on the real server: on the fixture, coverage is incomplete andavgSnris null. These paths are covered by the unit tests and the Goprevioustests only. - The 51-observer compactness numbers in the PR body; I did not re-measure them.
- The hot-startup corner (unlimited retention,
hotStartupHoursbetween 24 and 48): not exercised. [A]Previous.CompleteusesretainedHistoryFrom, whichverifyRetainedHistoryproves by comparing in-memory and DB counts from that edge, so it cannot claim the earlier window without having loaded it. - No staging or production access, no server API key, no upstream writes.
git ls-remoteshowed head9ddb9d25and master5699b63eboth before and after the review.
Relates to #304
Points 1, 2 and 6 of the issue landed in #351 and #352. This PR implements the
remaining points 3 (status evidence), 4 (SNR attribution) and
5 (traffic metrics) on the My Mesh cards and the home health panel, and
builds on the observer identity #572 (#262) just added.
Plan
(AGENTS.md rule 4):
stats.lastAdvertis strictly the node's own ADVERT,stats.lastHeardis that advert or identity-safe relay evidence(whichever is newer),
stats.avgSnris already the mean over directreceptions since Node "Heard By" card: decide whether to show direct-RF observers (defer) #76, and
activity24his the rolling window from My Mesh: accurate 24h node activity and compact repeater observer cards #351.(
public/my-mesh-activity.js, the shape My Mesh: show observer identity, status and traffic separately from mesh role #262 established) so the cards andthe health panel cannot drift apart, and so every rule is unit-testable.
the card already fetches, never as a new request.
<details>, and the metric grid loses a tile.What changed
3. Status evidence
Last own advertandLast attributed relay activityare separaterows in a new
Status evidencedisclosure on each card. The relay row isonly filled where the evidence supports it: the payload exposes one combined
lastHeard, so relay activity is reported exactly when it is newer thanthe node's own advert, and otherwise says
none newer than the last own advertrather than repeating the advert's time.the badge the dot stays filled and green. When the only evidence recent
enough is traffic attributed to the node by a short wire-hash prefix, the
badge becomes
Active · relay-attributed, the dot is hollow and the card'saccent drops to the provisional colour — an ambiguous match never produces
a confident green. The disclosure explains that a prefix unique among
known relays can still belong to an unknown one, and that a recent advert
is not proof of continuous uptime.
getHealthStatus/getHealthLastHeard(roles.js,fix(health): apply role thresholds without weakening activity evidence #539) remain the single source of truth and are injectable; the helper
defines none and the test asserts it.
4. SNR
Excellent / Good / Marginal / Poorverdict and its colour are removedfrom both the card and the health panel. Until Node "Heard By" card: decide whether to show direct-RF observers (defer) #76's attribution decision
is made, the card explains the metric instead of grading the repeater.
Direct SNR · n=42), andthe value is only shown when at least one direct reception proves it belongs
to this node — a mean with no direct attribution behind it would describe
whichever node the observer actually heard.
stats.avgSnritself is untouched: no shared health API metric isredefined.
5. Traffic
Packets today(a UTC calendar-day counter) becomesTransmissions · 24 h, the rolling window's own count, with thepopulation and window in the tooltip and as a
Counting windowrow in thedisclosure (a
titleis not reachable by touch).≥ 35) and an unknown windowas an em dash — never as a measured zero.
activity24hgainsprevious(windowStart,windowEnd,count,complete): the equally long window right before the rolling one. Thecomparison is shown only when both windows are proven complete, a
missing history is reported as
no comparable 24 h history, and a zerobaseline yields an absolute delta, never a division.
Avg hopsleaves the headline grid for the disclosure asAvg observed path length, explained as the mean hop count of theobserved paths of the node's transmissions — not a distance and not health.
Nothing labels node-associated traffic as forwarded traffic.
Cost and compactness
already makes (
/api/nodes/{key}/health?include=advertIntervals); the fiveapi(call sites inhome.jsare unchanged and pinned by a test.previousis filled by the same single pass over thenode's index that already fills the hourly buckets. The only added cost is
the RFC3339 parse for rows 24–48 h old that the string pre-filter used to
reject — the same order as the per-transmission work
GetNodeHealthalreadydoes for every row (it calls
heard.addon each). No new scan, no new lock,no new allocation per row.
every width (it was four over two rows below 600 px) and the explanation
is a closed
<details>. Measured on the 51-observer repeater card:1280 px 337.8 → 343.8, 390/375 px 349.1 → 329.1 — still inside the
< 350bound My Mesh: accurate 24h node activity and compact repeater observer cards #351 pinned, and lower than before on mobile.
Testing
Red before, green after for every point, each with at least one mutant; see
the report comment for the full table. Locally:
go test ./...incmd/serverandcmd/ingestor,sh test-all.sh(271 files),node test-frontend-helpers.js, and the affected Playwright suites against alocal Go server on the committed
e2e-fixture.dbprepared as in CI.