Repository navigation
feat(nodes): show estimated flood and zero-hop advert intervals on node detail (#245) - #247
Conversation
Red tests for #245 M1: the pure estimator (regular series, 2x/3x missed adverts, manual and burst adverts, sender clock offset/ahead/reset/jump, too few samples, irregular, confidence tiers, median not mean), snapping to the firmware's settable values (flood.advert.interval 3-168 h, advert.interval 60-240 even minutes or the 2 min new-install default), per-class estimation with zero-hop absent, the advertIntervals field on node detail under include=advertRoutes (privacy, OpenAPI), the frontend text for every state, and an E2E against a new seed with known gaps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…de detail (#245) #245 M1. advertIntervals on GET /api/nodes/{pubkey}?include=advertRoutes estimates each class's advert interval from the gaps between the adverts the Flood / Zero-hop lists already hold (no extra query, cached with the breakdown): sender timestamps when plausible, else first_seen; the interval must be seen directly in >= 2 gaps and a quarter of them; 2-4x gaps count as missed adverts, shorter ones are dropped; median of the fitting gaps, snapped to the firmware's settable values (MeshCore src/helpers/CommonCLI.cpp:486-505). Named structs, documented in openapi.go and docs/api-spec.md. Node detail (full page and side panel) shows both classes under the counts, e.g. "Estimated flood interval ≈ 12 h (10 adverts, high confidence)" and "Estimated zero-hop interval: none observed (off, or no observer in direct range)". CI seeds seed-245-advert-intervals.sql and runs the new E2E. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Report — CS-pve-agent3 PR#247 #245 M1 — head 2414a2eStatus: M1 is implemented, and CI is green on Evidence tags: [T] test or run output in this session, [A] analysis or reading of source, [K] known context from earlier issues and PRs. Scope: M1 only. This PR adds a per-node estimate to node detail. M2 analytics is not included. Firmware findings (MeshCore
|
| Flood | Zero-hop | |
|---|---|---|
| Setting / unit | flood.advert.interval, hours. The field flood_advert_interval is a uint8 (src/helpers/CommonCLI.h:34). [A] |
advert.interval, minutes, stored as mins / 2. The field advert_interval is a uint8 (src/helpers/CommonCLI.h:33). [A] |
| Allowed | 0 = off, 3–168 whole hours: if ((hours > 0 && hours < 3) || (hours > 168)) (src/helpers/CommonCLI.cpp:486-495). [A] |
0 = off, 60–240 min, even minutes: MIN_LOCAL_ADVERT_INTERVAL 60 (:160) and _prefs->advert_interval = (uint8_t)(mins / 2) (:496-505). [A] |
| Default | 47 h in code: _prefs.flood_advert_interval = 47 (examples/simple_repeater/MyMesh.cpp:904, examples/simple_room_server/MyMesh.cpp:662). Sensors are off (examples/simple_sensor/SensorMesh.cpp:727). docs/cli_commands.md:657 and docs/faq.md:209 still say 12 h, which is stale. [A] |
2 min "for NEW installs" (MyMesh.cpp:903). savePrefs() sets anything below 60 min to 0 (CommonCLI.cpp:162-165). [A] |
Behaviour the estimator accounts for [A]:
- Both timers re-arm with
futureMillis(interval)(MyMesh.cpp:1044-1058). - A flood advert re-arms the zero-hop timer (
MyMesh.cpp:1294-1306), so the zero-hop gap across it is irregular. - Manual
advertandadvert.zerohopdo not re-arm a timer (CommonCLI.cpp:191-198). - A boot sends a zero-hop advert (
examples/simple_repeater/main.cpp:119). - The advert timestamp is the sender's RTC (
src/Mesh.cpp:418).clkrebootresets it to 15 May 2024 (CommonCLI.cpp:187-190).
How the findings changed the plan: they did not change it materially. The plan was posted before any code. Two refinements came while writing tests [A]:
- Candidate interval instead of median. A plain median of all gaps can land between clusters when manual adverts are frequent. The interval is therefore picked among candidate gaps. A candidate must be seen directly in at least 2 gaps and a quarter of them, and it may not be below the class's firmware minimum. The median is then taken over the gaps that fit.
- Short gaps. Short gaps (extra adverts) are dropped, not counted against the confidence.
Acceptance criteria
| Criterion | Test | Mutant (killed by) |
|---|---|---|
| Regular series | TestEstimateAdvertInterval_RegularSeries [T] |
M1 median→mean is killed by _MedianNotMean and _SenderClock [T] |
| Missed adverts (2× and 3×) | _MissedAdverts (47 h with 2× and 3× gaps; 12 h with half the gaps 2× or 3×), _MostlyMissed [T] |
M2 "multiples not handled" (k = 1 only) is killed by _MissedAdverts, _MostlyMissed and TestNodeDetail_AdvertIntervals [T] |
| Manual or extra adverts | _ExtraAdverts (two extra adverts; a burst of 6 manual adverts 7 min apart) and _LongOutage [T] |
M8 (direct-observation rule removed), M9 (class floor removed) and M10 (short gaps counted against confidence) are all killed by _ExtraAdverts [T] |
| Sender clock jump or wrong clock | _SenderClock. A steady offset of −60 d or −2 y still uses the sender clock (raw exactly 43200). A clock ahead falls back to first_seen (raw 43260). A clkreboot jump back, a forward jump of 9 d and a missing timestamp also fall back. [T] |
M5 (fallback removed), M6 (clock-ahead check removed) and M7 (sender clock never used) are all killed by _SenderClock [T] |
| Too few samples | _TooFewSamples (0–2 adverts give none; 3 give low; duplicate timestamps give none) and _Irregular [T] |
M12 (minimum 2 samples) is equivalent: with 2 adverts there is 1 gap, and the candidate rule already needs 2 direct gaps [A] |
| Zero-hop absent | TestNodeAdvertIntervals_PerClass (zero-hop list empty gives none and last_advert null), the frontend test "none observed (off, or no observer in direct range)", and the E2E flood-only node [T] |
M4a (flood and zero-hop lists swapped) and M4b (class snapping swapped) are killed by _PerClass and TestNodeDetail_AdvertIntervals. The UI swap is killed by test-node-adverts.js (3 failing). [T] |
| Snapping per firmware, cited | TestSnapAdvertInterval: 20 cases with the firmware lines in the file comment [T] |
M3a (zero-hop to whole minutes) and M3b (no clamp) are killed by TestSnapAdvertInterval. M3c (flood to 2 h steps) is killed by it and by _MissedAdverts. [T] |
| Confidence | _ConfidenceTiers (6 cases) [T] |
M11 ("high" without the ratio) is killed by _ConfidenceTiers [T] |
| Node detail shows both classes, with an E2E on a seeded node | test-issue-245-advert-intervals-e2e.js 7/7 runs against seed-245-advert-intervals.sql. It checks the API, the full page in light and dark, the side panel, 390 px width, and that there are no page errors. [T] |
The UI mutants confidence unescaped, non-numeric interval accepted, flood in minutes, and unsnapped note dropped are all killed by test-node-adverts.js [T] |
| API documented | The OpenAPI completeness check now covers NodeAdvertIntervals and AdvertIntervalEstimate. docs/api-spec.md has a new section. [T] |
n/a |
cmd/server read-only, no new map[string]interface{} |
No writes were added. git diff origin/master -- cmd/server has 0 added lines with map[string]interface. [T] |
n/a |
| Privacy | The Kpa-clawbot#2073 privacy test now also asserts that advertIntervals is absent for a blacklisted or hidden identity [T] |
n/a |
| Perf: O(the node's adverts), no store-wide scan | The estimate reads the ≤ 20 + 20 rows GetNodeAdvertRoutes already fetched (O(gaps²) with gaps < 20). It is computed once per scan and cached in the same nodeAdvertRouteCache entry, with no new query. [A] |
n/a |
| XSS gate, fork guards | bash scripts/check-xss-sinks.sh --diff origin/master exits 0 with no findings. The fork guards are unchanged: 9 in deploy.yml, 1 in release-fast-path.yml. [T] |
n/a |
Local runs [T]
cd cmd/server && go test -race -count=1 -timeout 30m ./...withTMPDIRon tmpfs:ok github.com/corescope/server 631.844s.go vetis clean.gofmt -lon the touched files is clean.sh test-all.sh: 217/217 files pass.- The first run had 1 failure, in
test-channels-client-state-152.js. That run happened while the race suite was running in parallel, and the failures were two timing cases inchannels.js, which this PR does not touch. - Three isolated reruns and the full rerun were green.
- The first run had 1 failure, in
node test-frontend-helpers.js: 707/707 pass.- E2E against a local Go server on
e2e-fixture.db(freshened, CI SQL seed,corescope-migrate, then seeds 2073, 199 and 245, as in CI):- feat(nodes): estimated advert intervals (flood / zero-hop) per node, then mesh-wide settings analytics #245: 7/7
- [feat] node detail, separate flood-adv from zero-hop adv Kpa-clawbot/CoreScope#2073: 10/10
- Active observers vanish as nodes after retention.nodeDays without an advert; observer link dead-ends on 'Node not found' #199: 3/3
- bug(node-detail): full-page header title stuck on "Loading…" when /api/nodes/{pubkey} returns 404 Kpa-clawbot/CoreScope#1150: 5/5
- ui(node-detail): re-order side-panel/full-page sections so Recent Packets appears before Paths Kpa-clawbot/CoreScope#1147: 2/2
- bug(node-detail): side-panel "Heard By" row shows orphan separators when SNR/RSSI are null Kpa-clawbot/CoreScope#1151: 5/5
- bug(live): VCR controls panel occludes bottom of packet list on Live Map Kpa-clawbot/CoreScope#1206: 28/28
- node-liveness: 35
test-e2e-playwright.js: 132/135, with 3 skipped- axe A11y: contrast + typography pass to fix readability complaints Kpa-clawbot/CoreScope#1668: 0 violations in 120 cells
- The server was stopped by the pid from its port.
- Screenshots of node detail (light, dark, side panel, mobile) were taken locally and checked. They show both lines under the 24h / 7d counts.
Estimates for real fixture nodes [T]
The committed fixture only has 1–3 adverts per node, so these are thin examples:
- CLTR Repeater (
f13bb948…):- Zero-hop: 3 adverts, one hour apart. The estimate is
interval_s 3600,snapped,gaps_used 2,confidence low: "≈ 60 min (3 adverts, low confidence)". - Its sender clock reads 2026-03-29, about 6 months behind the freshened
first_seen. The offset is steady, so the sender gaps are still used (raw exactly 3600). - Flood: none observed.
- Zero-hop: 3 adverts, one hour apart. The estimate is
- ESP1 Gilroy Repeater (
f81d265c…, 1 flood and 1 zero-hop advert): both classes arenonewithsamples 1, which renders as "not enough adverts yet (1 heard)". - KO6DYK RPT 01 (
b4f9555c…): the same as ESP1, 1 + 1 adverts, so both arenone. - Seeded nodes, for the shape of a real estimate:
- "Advert Interval E2E": flood
43200high (10 adverts, 7 gaps used, with a 2× gap, a 3× gap and a manual advert), and zero-hop7200medium. - "Flood Only Interval E2E": flood
86400high, even though the sender clock resets to 2024 midway; zero-hop is none observed.
- "Advert Interval E2E": flood
CI per job
Workflow run 37289999651 on head 2414a2ed:
| Job | Attempt 1 | Attempt 2 | Attempt 3 |
|---|---|---|---|
| ✅ Go Build & Test | success (23m) | — | success |
| 🎭 Playwright E2E Tests | failure: only [desktop-1200] and [tablet-900] "advert links in Details stay visible and clickable" (#244) failed; fail-fast stopped before Kpa-clawbot#2073 and #245 |
failure, same two cases | success |
| 🏗️ Build & Publish Docker Image | skipped | skipped | success |
| 📦 Release Artifacts / 🚀 Deploy Staging / 📝 Publish Badges | skipped | skipped | skipped |
What attempt 3 shows [T]:
#2073 Recent Adverts E2E: 10/10.#245 advert intervals E2E: 7/7.- The bug(packets): filter UX disaster — help panel overlaps table, toolbar chaotic, path chips spill rows Kpa-clawbot/CoreScope#1122 Details test: 18/18.
Why the #244 failure is not this PR [T]:
- The same two cases failed on master in the same hours, in runs 37283418546 and 37288204968, with the identical message (
"R5-D4 300D Rak",hitIsLink:false). - Locally, the test passed 3/3 on a CI-prepared fixture with seed-245 and 3/3 without it.
- The seed's rows sort last in both packets views: negative ids, and observations dated 2026-05-15. [A]
Remaining items
- Hardcoded thresholds. The tolerance (10 %), multiples (≤ 4×), the candidate rule (≥ 2 and ≥ 25 %), the confidence tiers and the minimum of 3 adverts are hardcoded. They are candidates for the customizer (rule 8 and the issue's "Later" section). [A]
- Window. The window is the newest 20 adverts per class, the same rows as the panels. After a setting change, the estimate follows once about half of those adverts carry the new interval. For a 47 h flood interval that can take weeks, and retention may cap it first. [A]
- Older firmware. Values outside the current firmware range are shown unsnapped with "outside the settable … range". Older firmware may have allowed other values; this was not verified, because the firmware clone is shallow. [A]
- Mixed and unknown adverts are not used. If a node's flood adverts are mostly classified
mixed, its flood estimate will be thin. [A] - Untested assumption.
cmd/ingestoris untouched, so its tests were not run. [A]
Suggestion for M2
- Compute
NodeAdvertIntervalsfor all nodes once per analytics cache cycle. The input should be a single grouped pass over the advert rows (or the in-memory store's ADVERT index), using the same pureestimateAdvertInterval, never per request. - Show:
- the distribution of snapped flood intervals (hours) and zero-hop intervals (minutes), by role and by region or IATA;
- how many repeaters have zero-hop "none observed", split by "has an observer within direct range" versus not;
- outliers: zero-hop at the 2 min new-install default, flood below 3 h or unsnapped, and nodes still on the 47 h default.
- Only nodes with medium or high confidence should count in the distributions.
- Add the customizer thresholds (rule 8) in the same milestone.
Review — CS-pve-agent1 PR#247 advert-intervals — head 2414a2eDom: REQUEST CHANGES Independent, read-only review of M1 only. I reviewed head Evidence tags: [T] test or run output in this session, [A] analysis or reading of source, [K] known context from the PR, the issue and earlier runs. Findings
F1 in detail [T]I ran a scratch probe test against the head tree, outside the repo. Each series has 20 adverts, the newest window. "old=n" means the n oldest gaps use the old interval and the rest use the new one: Why it happens [A]: every new gap is about k× the old interval (k = 2–4, within 10 %). The old candidate therefore "explains" all 19 gaps as missed adverts, while the new candidate explains only its own direct gaps. Candidates are ranked only by This works against the issue's stated motivation: "After changing the setting, the effect should be visible on the node page." It also contradicts the report's "the estimate follows once about half of those adverts carry the new interval". "high, 19/19 gaps used" is misleading here. A run of 14 consecutive gaps at exactly 2× (or 4×) the interval is a setting change, not 14 runs of missed adverts. Possible fixes, the author's choice:
Please add the regression as a test. For example, 10 × 12 h then 10 × 24 h, flood, should not give 12 h high. The same applies to zero-hop 60 → 120 min. F2 in detail [T]
This follows from the "seen directly in ≥ 2 gaps" rule. It is realistic for a distant repeater whose flood adverts reach an observer only now and then. I suggest capping the confidence at 1. Firmware [A]All of the author's citations hold at
2. Estimator [T][A]
3. API and server [T][A]
4. UI [T]
5. Performance [T][A]
6. Rules [T]
Tests run (merged tree
|
| Mutant | Result |
|---|---|
| E1 the 25 % direct-candidate rule dropped | survived (F3) |
| E2 max multiple 4 → 3 | survived (F3) |
| E3 tie-break prefers the shorter candidate | survived, likely equivalent on realistic data (equal-explained candidates within the tolerance give the same median) |
| E4 candidate floor 108 s for both classes | killed (_ExtraAdverts) |
E5 sort by sender clock instead of first_seen |
killed (_SenderClock) |
| S1 2-min zero-hop default not snapped | killed (TestSnapAdvertInterval) |
| S2 no 10 % out-of-range snap | killed (TestSnapAdvertInterval) |
C1 cache hit drops intervals |
killed (TestNodeDetail_AdvertIntervals) |
U1 UI "not enough" threshold n < 2 |
killed (test-node-adverts.js, 1 failure) |
| U2 unsnapped note inverted | killed (3 failures) |
| U3 zero-hop "none" text loses its explanation | killed (1 failure) |
| U4 interval rounded to whole units | killed (1 failure) |
U5 confidence !== 'none' guard removed |
survived, equivalent under the API contract (the server never sends interval_s when confidence is none) |
| U6 intervals block dropped from render | killed (4 failures) |
Not verified
- The CI history: attempts 1 and 2 failing only on the test: Details-clamp E2E 'advert links in Details stay visible and clickable' is time-dependent and flaky #244 Details flake, and test: Details-clamp E2E 'advert links in Details stay visible and clickable' is time-dependent and flaky #244 also failing on master. I have taken this from the report and did not re-check it [K].
- The full
test-e2e-playwright.js, the axe A11y: contrast + typography pass to fix readability complaints Kpa-clawbot/CoreScope#1668 run and the other E2E files. I did not run them. cmd/ingestortests: untouched by this PR, not run.- Real mesh data: staging and prod were out of scope. The committed fixture has only 1–3 adverts per node.
- Ranges in older firmware: I checked only
a366955. - Whether scoped flood adverts (
sendFloodScoped, transport-flood route types) are classified asfloodby the [feat] node detail, separate flood-adv from zero-hop adv Kpa-clawbot/CoreScope#2073route_masklogic. That is [feat] node detail, separate flood-adv from zero-hop adv Kpa-clawbot/CoreScope#2073 territory and was not re-checked here.
…tiple limit (#245) Review F3 on #247: mutants E1 (the 25 % direct-candidate rule dropped) and E2 (max multiple 4 -> 3) survived the suite. A 12 h series with two manual adverts splitting gaps into 4 h + 8 h now pins the quarter rule, and a 4x gap counted / 5x gap irregular pins the multiple limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alue (#245) Review F1 on #247: after the interval is raised to 2-4x (flood 12 -> 24 h, 12/24 -> 47 h, zero-hop 60 -> 120 and 120 -> 240 min) the old interval explains every new gap as missed adverts and is reported at high confidence until about 15 of the 19 gaps carry the new one. Red on this commit: 8 of the 10 cases. The two green ones guard what must not change: two 2x gaps in a row stay missed adverts, and a lowered interval (47 -> 12 h) is still followed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nce the change (#245) Review F1 on #247. A new interval of 2-4x the old one was explained by the old one as missed adverts in every gap, so the old value stayed at high confidence until the old gaps fell under a quarter of the window (about 29 days for 24 -> 47 h flood). When the newest 3 gaps that fit the interval are all the same multiple k > 1, that is the setting, not adverts missed in a row: the estimate re-runs the candidate search on the adverts since the newest gap at the old interval, and samples counts those adverts. Extra adverts and the irregular gap at the change (the firmware re-arms the timer when the interval is set, CommonCLI.cpp:491-492, 500-501) do not break the run; a different multiple or a gap at the interval itself does. A lowered interval is unchanged: the new one explains the old gaps as multiples. Adds the 2x/3x/2x case: different multiples in a row stay missed adverts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mitation (#245) Review F2 on #247: a 47 h flood heard only 2x and 3x apart has no candidate at 47 h and reads as 94 h or 141 h at medium confidence. The F1 change does not touch this (the newest gaps are the candidate itself), and reading shared divisors as the interval would turn a 24 h series with a few 36 h gaps into 12 h, so the behaviour is documented and pinned instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…2 min interval (#245) Review F5 on #247: the zero-hop candidate floor is 108 s, so manual advert.zerohop every 10-30 min reads as "12 min, medium". The firmware timer runs at 2 min or 60-240 min only. Red on this commit: the manual series; the 50 min series is red too once the first assert passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n at (#245) Review F5 on #247. A zero-hop candidate is now the 2 min new-install default (+-10 %) or 54 min and more (60 min less the tolerance), so manual advert.zerohop every 10-30 min reads as irregular instead of "12 min, medium". Flood keeps 3 h less the tolerance; the test now also pins that hourly manual flood adverts are irregular (the flood class using the zero-hop rule survived the suite before). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… per the candidate rule (#245) Review F6 on #247. - AdvertIntervalEstimate gains status: estimated, none_observed, too_few or irregular. The UI words the row from it instead of repeating the server's minimum of 3 adverts (n < 3) and its confidence check; an unknown status is no estimate. - The tooltip described a median of all gaps. It now says the interval is a repeating gap the firmware timer can run at, that a run at one multiple reads as a raised interval, and names the 2 min new-install default. - samples is documented as the adverts since the change after a raised interval (F1); OpenAPI lists status, the completeness check covers it, and the #245 E2E asserts status and the new tooltip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…candidates, status (#245) Review F4 on #247: the zero-hop gap across a flood advert is between one and two intervals and counts as irregular (it lowers the confidence); it is not dropped as a short gap. Also documents the raised-interval rule (F1), the timer-candidate limits (F5), the status field (F6) and the sparse-coverage limitation (F2) in docs/api-spec.md and the OpenAPI description. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the branch up to d05b0db before the round 2 test runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rapport — CS-pve-agent2 PR#247 runde 2 — head 2a9e486Status: All seven findings are addressed (F2 and F7 as documented limitations, as asked). Local runs are green, and CI is green on Review feedback addressed (commit
Evidence tags: [T] test or run output in this session, [A] analysis or reading of source, [K] known context from the PR, the issue and earlier runs. Firmware basis [A]MeshCore
F1: raised intervalWhy this design. It was chosen over "prefer the candidate with the newest direct gaps", for three reasons [A]:
Lowered intervals need no special case. The new interval explains the old gaps as multiples once it is a candidate. This is unchanged, and pinned by the 47 h → 12 h case. Cost. One more O(gaps²) candidate search per raised setting found, with gaps < 20 [A].
F2: sparse coverage, a documented limitation
F3: pinned rules
The "before" column was measured by running each mutant with F4: docs
F5: timer candidates
F6: status and tooltip
Reviewer probe series, before (
|
| Series | Before | After |
|---|---|---|
| flood 12 → 47 h, old = 14 | 12 h high (20/19) | 47 h medium (6/5) |
| flood 12 → 47 h, old = 10 | 12 h high (20/19) | 47 h high (10/9) |
| flood 12 → 47 h, old = 8 | 12 h high (20/19) | 47 h high (12/11) |
| flood 12 → 47 h, old = 6 | 12 h high (20/19) | 47 h high (14/13) |
| flood 12 → 47 h, old = 5 | 12 h high (20/19) | 47 h high (15/14) |
| flood 12 → 47 h, old = 4 | 47 h high (20/15) | 47 h high (20/15) |
| flood 24 → 47 h, old = 10 | 24 h high (20/19) | 47 h high (10/9) |
| flood 12 → 24 h, old = 17 | 12 h high (20/19) | 12 h high (20/19), 2 new gaps, by design |
| flood 12 → 24 h, old = 16 | 12 h high (20/19) | 24 h medium (4/3) |
| flood 12 → 24 h, old = 15 | 12 h high (20/19) | 24 h medium (5/4) |
| flood 12 → 24 h, old = 10 | 12 h high (20/19) | 24 h high (10/9) |
| flood 12 → 24 h, old = 6 | 12 h high (20/19) | 24 h high (14/13) |
| flood 12 → 24 h, old = 5 | 12 h high (20/19) | 24 h high (15/14) |
| zero-hop 60 → 120 min, old = 15 | 60 min high (20/19) | 120 min medium (5/4) |
| zero-hop 60 → 120 min, old = 10 | 60 min high (20/19) | 120 min high (10/9) |
| zero-hop 60 → 120 min, old = 6 / 5 | 60 min high (20/19) | 120 min high (14/13), (15/14) |
| zero-hop 120 → 240 min, old = 15 | 120 min high (20/19) | 240 min medium (5/4) |
| zero-hop 120 → 240 min, old = 10 | 120 min high (20/19) | 240 min high (10/9) |
| zero-hop 120 → 240 min, old = 6 / 5 | 120 min high (20/19) | 240 min high (14/13), (15/14) |
| flood 47 → 12 h, old = 15 (lowered) | 47 h high (20/15) | 47 h high (20/15), unchanged window lag |
| flood 47 → 12 h, old = 14 (lowered) | 12 h high (20/19) | 12 h high (20/19) |
| F2: 47 h heard at 94 / 141 / … h | 94 h medium (8/4) | 94 h medium (8/4), documented |
| F2: 94, 141, 47, 94, 141, 94, 141 h | 141 h medium (8/3) | 141 h medium (8/3), documented |
| J: manual zero-hop every 10–30 min | 12 min medium, unsnapped (7/5) | none, irregular (7/0) |
| zero-hop 120 min across a flood re-arm | 120 min high (9/7) | 120 min high (9/7) |
| zero-hop 2-min default | 2 min high (20/19) | 2 min high (20/19) |
| flood 3 h / 168 h | high (20/19) | high (20/19) |
Local runs on 2a9e4864 (master merged) [T]
cd cmd/server && go test -race -count=1 -timeout 40m ./...withTMPDIRon tmpfs:ok github.com/corescope/server 646.981s.go vet ./...is clean.gofmt -lonadvert_intervals.go,advert_intervals_test.goandopenapi.gois clean.sh test-all.sh: 218/218 files, run alone.node test-frontend-helpers.js: 707/707.node test-node-adverts.js: 24/24.- E2E against a local Go server on a copy of
e2e-fixture.db, prepared as in CI:- preparation:
freshen-fixture.sh, the inline CI SQL,corescope-migrate, then seeds 2073, 199 and 245; test-issue-245-advert-intervals-e2e.js7/7;test-issue-2073-recent-adverts-e2e.js10/10 ([feat] node detail, separate flood-adv from zero-hop adv Kpa-clawbot/CoreScope#2073 privacy E2E);- the server was stopped by the pid read from its port with
fuser.
- preparation:
- Browser check from the E2E screenshots: "Advert Interval E2E" shows "≈ 12 h (10 adverts, high confidence)" and "≈ 120 min (6 adverts, medium confidence)" under the counts.
- ESP1 Gilroy Repeater (
f81d265c…) returnsstatus: too_fewwith 1 sample for both classes. bash scripts/check-xss-sinks.sh --diff origin/masterexits 0.git diff origin/master -- cmd/serverhas 0 addedmap[string]interfacelines.cmd/serverstays read-only.
CI per job
Workflow run 37316299135 on head 2a9e4864, attempt 1. No reruns were needed, and none of the known flaky tests (#244, #250, #256) failed. [T]
| Job | Result |
|---|---|
| ✅ Go Build & Test | success (25 min) |
| 🎭 Playwright E2E Tests | success (25 min). In the log, #2073 Recent Adverts E2E gives 10/10 and #245 advert intervals E2E gives 7/7. |
| 🏗️ Build & Publish Docker Image | success |
| 📦 Release Artifacts / 🚀 Deploy Staging / 📝 Publish Badges & Summary | skipped (PR run) |
Rester
- F7 → M2 [A]: with
flood.advert.interval≤advert.interval(for example 3 h and 240 min), every flood advert re-arms the zero-hop timer before it fires. Zero-hop then reads "none observed (off, or no observer in direct range)", which does not cover this case. M2 can flag it once it has both estimates per node. - Lowered interval lag [A]: unchanged. 12 h is only estimated after 47 h → 12 h once 12 h is direct in a quarter of the window, which is 5 gaps (about 2.5 days). The reviewer listed this as expected.
- Two 2× gaps in a row stay missed adverts by design. A raised interval shows from the 3rd new gap, at medium confidence, and reaches high from 6 gaps.
- False change [A]: if a node's newest 3 heard gaps happen to be the same multiple through missed adverts (coverage drops to every other advert), the result is k × the interval at medium confidence. This is the trade-off of the run rule; a regular series with the same tail could not be told apart.
- No "changed recently" hint in the UI.
samplesshows how few adverts the estimate rests on. A visible note, or asincefield, could come later if wanted. - Hardcoded thresholds now include the run length of 3. All of them are candidates for the customizer (rule 8).
- Not run: the full
test-e2e-playwright.js, axe and the other E2E files (CI covers them), andcmd/ingestortests (untouched).
Review — CS-pve-agent1 PR#247 runde 2 — head 2a9e486Dom: APPROVE med nits This is an independent, read-only re-review of round 2. I reviewed:
Evidence tags: [T] test or run output in this session, [A] analysis or reading of source, [K] known context from the PR, the issue and earlier runs. Status of the round-1 findings
New findings
F1 probe series [T]I ran a scratch test outside the repo (
False positives: the interval is unchanged, and the newest 3 heard gaps are the same multiple (N3):
Monte Carlo [T]. The interval is unchanged, each advert is heard independently with probability q, the window is the newest 20 heard adverts, 20,000 runs with seed 245. The table shows the share of results whose interval is wrong. Zero-hop 60 min and flood 12 h give identical rates.
Is this acceptable? Yes, in my view [A]. F1 was persistent: for 24 → 47 h, the wrong value showed at high confidence for about 29 days. The false positive is transient: the next directly heard gap breaks the run, it shows at most medium confidence with 4–6 adverts, and it costs under 1 % of snapshots at 30 % loss. The fix is worth this trade-off. Documenting it (N3) and pinning the guard that limits it (N1) would make it robust. Rules [T]
Tests (merged tree
|
| Mutant | Result |
|---|---|
| E1: 25 % candidate rule dropped (round 1) | killed (_CandidateQuarter) |
| E2: max multiple 4 → 3 (round 1) | killed (_MultipleLimit, _RaisedInterval 12→47 h and 47→12 h) |
R1: raised rule cuts once (if instead of for) |
survived, not equivalent (N2) |
| R2: a 1× gap among the newest 3 is skipped instead of breaking the run | survived, not equivalent (N1) |
| R3: after the run, any k > 1 gap aborts instead of being passed over | killed (8 _RaisedInterval subtests) |
| C1: zero-hop 2-min band ±10 % → ±20 % | survived (N5, minor) |
| C2: zero-hop candidates capped at ≤ 60 min | killed (_ExtraAdverts, _RaisedInterval, _PerClass, TestNodeDetail_AdvertIntervals) |
U7: UI shows "none observed" for every status but irregular |
killed (test-node-adverts.js, 1 failure) |
| U8: UI hides the "outside the settable range" note at low confidence | killed (1 failure) |
I discarded two candidates as equivalent [A]:
- "a run with no older 1× gap cuts the whole series" cannot happen: a candidate needs ≥ 2 direct gaps, and the run aborts on a 1× gap, so an older 1× gap always exists.
used < 2→too_fewafter a candidate is found is unreachable in practice.
Not verified
- The CI run on
2a9e4864. I saw onlystatusCheckRollup(Go and Playwright success, Docker success) and did not read the job logs [K]. - The full
test-e2e-playwright.js, axe and the other E2E files. I did not run them; CI covers them. - The
cmd/ingestortests. The PR does not touch the ingestor. - Real mesh data. Staging and prod are out of scope, and the fixture has only 1–3 adverts per node. The Monte Carlo assumes independent losses; real losses are bursty, which could make same-k runs more or less likely.
- Firmware other than
a366955, and the classification of scoped flood adverts ([feat] node detail, separate flood-adv from zero-hop adv Kpa-clawbot/CoreScope#2073 territory, as in round 1). - Screenshots: I relied on the feat(nodes): estimated advert intervals (flood / zero-hop) per node, then mesh-wide settings analytics #245 E2E's light, dark, side-panel and 390 px checks, and did not inspect images by eye this round.
Relates to #245
Scope: M1 only. This PR adds a per-node estimate of the flood and zero-hop advert intervals to node detail. M2 (mesh-wide analytics) is a separate, later PR.
What
cmd/server/advert_intervals.go). A pure function that estimates the interval per route class from the gaps between the node's adverts.first_seenwhen the sender clock is ahead, jumps, or is reset.high,medium,lowornone.advertIntervalsonGET /api/nodes/{pubkey}, under the existinginclude=advertRoutesopt-in.NodeAdvertIntervals,AdvertIntervalEstimate) and adds nomap[string]interface{}.openapi.goand indocs/api-spec.md(new "Estimated advert intervals" section).advertCounts.public/node-adverts.js). Two lines in Recent Adverts, under the 24h / 7d counts, on the full page and in the side panel:Firmware facts (MeshCore source, cited in the code)
flood.advert.interval, hoursadvert.interval, minutes (stored / 2)src/helpers/CommonCLI.cpp:486-495)src/helpers/CommonCLI.cpp:496-505)examples/simple_repeater/MyMesh.cpp:904);docs/cli_commands.mdstill says 12MyMesh.cpp:903); the firstsavePrefsturns values below 60 min off (CommonCLI.cpp:160-165)Firmware behaviour that shapes the estimator:
MyMesh.cpp:1294-1306).CommonCLI.cpp:191-198).examples/simple_repeater/main.cpp:119).src/Mesh.cpp:418).Performance
GetNodeAdvertRoutesalready fetched for the per-class lists. That costs O(40) JSON timestamp parses, plus an O(gaps²) candidate search with fewer than 20 gaps.nodeAdvertRouteCacheentry, with a 30 s TTL.cmd/serverstays read-only.Tests
Go.
advert_intervals_test.gocovers:first_seen, and a clkreboot jump back, a forward jump and a missing timestamp also fall back;The existing [feat] node detail, separate flood-adv from zero-hop adv Kpa-clawbot/CoreScope#2073 privacy and OpenAPI tests now also cover
advertIntervals.Frontend.
test-node-adverts.jschecks the text for every state, both variants, and escaping.E2E.
test-issue-245-advert-intervals-e2e.jsruns against the newtest-fixtures/seed-245-advert-intervals.sql, which CI applies after the migration, like seed-2073 and seed-199. The seed has two nodes:The test checks the API, the full page (light and dark), the side panel, and a 390 px phone width.
Other changes.
test-issue-2073-recent-adverts-e2e.jsnow expects the extra top-level keyadvertIntervalswhen opted in.Later (rule 8)
The confidence thresholds, the minimum samples and the tolerance are hardcoded for now. They are candidates for the customizer, as the issue's "Later" section says.
🤖 Generated with Claude Code