Fixes#1726.
## Problem
A MeshCore v1.16.0 repeater configured for 2-byte path hashes
(`path.hash.mode=1`) — e.g. `36f6c7c7…` (`DK_3400_RAK_TEST`) — kept
showing as **"varies"** / mixed 1-byte + 2-byte for the full 7-day
advert window.
Per the live data in the issue triage: of the node's ~20 recent adverts,
exactly **one** (2026-06-09, across 15 distinct observer paths) was a
genuine 1-byte flood advert; every other advert was 2-byte. The
flip-flop heuristic in `computeNodeHashSizeInfo` weighs that stale
advert equally with recent ones, so an operator who flips
`path.hash.mode` mid-flight (or a single old 1-byte advert) stays
flagged for the full window with no way to signal "the config is settled
now."
## Fix
Two coupled changes in `cmd/server/store.go` `computeNodeHashSizeInfo`:
1. **Chronological ordering.** `byPayloadType[4]` iterates in insertion
order, not timestamp order, so `HashSize = Seq[last]` could pick the
wrong advert under out-of-order MQTT ingest or chunked cold-load (the
"carmack" concern from triage). We now collect `(FirstSeen, size)` pairs
and **stable-sort by `FirstSeen`**; ties keep insertion order,
preserving prior behavior when timestamps are equal.
2. **Recency decay.** After `transitions >= 2` raises the flip-flop
flag, clear it when the most recent `hashSizeRecentAgreeCount` (= **3**)
non-zero-hop adverts all agree on a single size. A node still flapping
(recent adverts disagree) stays flagged. `3` mirrors the existing
≥3-observation threshold used to raise the flag.
## Policy note
Triage marked this **needs-operator-input** because the decay is a
behavior/policy change. This PR implements the rule the triage proposed
("if the last 3 adverts agree, clear inconsistent"), which matches the
reporter's stated expectation. Happy to adjust the threshold or gate it
differently per your call.
## Tests
`cmd/server/issue1726_hash_decay_test.go`:
- `TestIssue1726_SettledNodeNotInconsistent` — reporter's case
(`[2,1,2,2,2]` within window) → `Inconsistent=false`, `HashSize=2`.
- `TestIssue1726_HashSizeUsesChronologicallyLatest` — out-of-order
insertion still reports the chronologically-latest size.
- `TestIssue1726_ActiveFlapperStaysInconsistent` — a node whose recent
adverts disagree stays flagged.
Existing flip-flop / hash-collision tests unchanged and green; full
`cmd/server` package suite passes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Erwin Fiten <e.fiten@opteco.be>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>