Greptile P2 on PR #92: with 64 tracked sources, evicting the oldest
entry discarded its open cooldown, so a flood of distinct bogus
identities reset other sources' windows and forced unbounded path
requests (each answered by every peer holding the announce).
- Per-identity cooldown 5 min -> 30 min.
- The table no longer evicts an open window: while all 64 slots hold
unexpired windows, never-before-seen identities are deferred until a
slot frees (at most one 30-minute window) instead of dropping
someone else's cooldown. Open windows are only ever pruned after
they expire.
- Aggregate bound: at most kMaxTrackedSources automatic path requests
per rolling 30-minute window, capping the worst-case network cost of
a rotating-identity flood.
Host tests extended: saturated-table deferral, 512-identity flood bound
(one window and across consecutive windows), lazy expiry/prune,
boundary checks at the 30-minute cooldown.
Move the rate-limit/cooldown/cap decision out of the UIManager.cpp
anonymous namespace into a pure, header-only policy
(UI/LXMF/UnknownSourceKeyRequest.h) so it is host-testable without the
ESP/microReticulum stack. UIManager keeps only the side effect
(Transport::request_path).
Adds tests/native/test_unknown_source_key_request.{cpp,py} (24 checks,
ASan+UBSan): new-source request, 5-min cooldown boundary, re-record
resets the window, per-source independence, 64-entry cap with oldest
eviction, and steady-state cost.