mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-30 20:58:03 +00:00
Option C from issue #1229: weight neighbor-graph edges by the number of distinct observers that contributed to them, so the disambiguator prefers corroborated edges over single-source ones at the same raw score. Stacks with the geo-rejection filter merged for #1228 to give two independent defenses against cross-region prefix-collision pollution. Formula (NeighborEdge.Confidence): c = min(1.0, max(1, |Observers|) / 3.0) - 1 observer -> 1/3 weight (suspect) - 2 observers -> 2/3 weight - >=3 -> 1.0 (saturated, full historical weight) Saturation at 3 is conservative: high enough that a single chatty observer cannot dominate, low enough that 3-observer corroboration in a normally-staffed region already counts as full confidence. Tier-1 score in resolveWithContext becomes Score(now) * Confidence(). The downstream ratio guard (best >= 3x runner-up) is unchanged — a 6-observer edge with 30 obs now beats a 1-observer edge with 25 obs by 3.6x (vs. 1.2x before), enough to trigger tier 1 and skip the geo fallback that was misresolving in the cross-region case. The Observers map[string]bool field already existed on NeighborEdge and was populated on every upsert; this PR is the first to consume it in the resolver. Backward compatibility (persistence): neighbor_edges schema is unchanged. The Observers set is rebuilt by BuildFromStoreWithOptions from live observations on every graph refresh (5-min TTL), so persisted edges only carry a stale empty set during the warm-up window after a restart. Confidence() defaults n to 1 when |Observers|==0, so legacy rows resolve as single-observer (degraded but non-zero) confidence rather than disappearing — defensive. Fixes #1229