Follow-up to #58. The "Unmanaged keepalived detected" panel it shipped never
appeared on any deployment. GET /discoveries was declared after GET /{vip_id} in
routers/vip.py, and FastAPI matches routes in declaration order, so every request
for the discovery list was routed into get_vip, which takes vip_id: int and
rejected "discoveries" with 422 before list_vip_discoveries ever ran.
The failure was completely silent. The agents reported their configs correctly,
the rows landed in vip_discoveries, and the HA/VIP page treats any non-OK
response as "nothing to show" - so the feature was invisible with no error in
any log. Confirmed against a real fleet: two discovery rows present in the
database, one with a parsed candidate, and an empty panel.
- move list_vip_discoveries above the /{vip_id} routes, with a comment stating
the ordering requirement
- add tests/test_router_path_shadowing.py: a static source scan that fails if
any literal path in any router is declared after a parameterised route that
would swallow it. The whole router tree is clean; the detector is itself
tested against the pre-fix ordering so the guard cannot pass vacuously.
POST /adopt is unaffected: no POST /{vip_id} route exists.
Also carries the release metadata for #58 (v1.10.4) and #60 (v1.10.5), which
are published together with this fix rather than as separate artifacts.
No schema, API-shape, frontend or agent change. Data reported under the earlier
code is not lost - existing rows show up as soon as this backend is deployed,
with no agent action needed.