What
The route-auth guard added in #17344 (autobot-backend/api/long_running_operations_17017_test.py, test_every_route_on_this_router_declares_a_gate) is sound in its enumeration but weak in two ways that let it degrade silently in future. Found during the pre-merge review of #17344; not blocking then, because neither leaves today's routes unchecked.
The guard's enumeration is deliberately good and should be preserved: it parses the module's own source with ast rather than walking FastAPI's live route table, which makes it immune to registration style (decorator, include_router, string tuple in feature_routers.py:418, package __init__). It also carries _MIN_ROUTES_SEEN = 10 so a silent-empty enumeration fails loudly — verified exact, not padded: long_running_operations.py has exactly 10 route decorators on origin/main.
Both problems below are about the guard's resistance to regression of itself.
1. _declares_a_gate matches names and keywords, not semantics
A route passes if either:
- any decorator carries a
dependencies= keyword at all — the list contents are never inspected, so dependencies=[Depends(some_unrelated_thing)] satisfies it; or
- the handler has a parameter literally named
current_user — its default is never inspected, so current_user: str = Query(...) satisfies it.
So a future route can look gated to this guard while being anonymous. The guard's own docstring discloses that it is "a structural check... not that any gate actually refuses anyone", so this is a stated limitation rather than a hidden one — but it is the single way the guard can pass on an ungated route, which makes it worth closing.
2. The RED case is not committed
#17344's body describes a manual mutation test — append an ungated route, watch it fail by name, revert. That procedure is not in the suite. Nothing in CI proves _declares_a_gate still fires: loosening the current_user name check, or a typo in _ROUTE_DECORATORS, would leave the guard green and blind, and only a re-run of the manual steps would catch it.
Worth contrasting with #17347, which committed its mutation proof as two tests driving the real collisions() against a synthetic checkout, plus a mutation of its own assertion. That is the shape this one needs.
Acceptance criteria
What
The route-auth guard added in #17344 (
autobot-backend/api/long_running_operations_17017_test.py,test_every_route_on_this_router_declares_a_gate) is sound in its enumeration but weak in two ways that let it degrade silently in future. Found during the pre-merge review of #17344; not blocking then, because neither leaves today's routes unchecked.The guard's enumeration is deliberately good and should be preserved: it parses the module's own source with
astrather than walking FastAPI's live route table, which makes it immune to registration style (decorator,include_router, string tuple infeature_routers.py:418, package__init__). It also carries_MIN_ROUTES_SEEN = 10so a silent-empty enumeration fails loudly — verified exact, not padded:long_running_operations.pyhas exactly 10 route decorators onorigin/main.Both problems below are about the guard's resistance to regression of itself.
1.
_declares_a_gatematches names and keywords, not semanticsA route passes if either:
dependencies=keyword at all — the list contents are never inspected, sodependencies=[Depends(some_unrelated_thing)]satisfies it; orcurrent_user— its default is never inspected, socurrent_user: str = Query(...)satisfies it.So a future route can look gated to this guard while being anonymous. The guard's own docstring discloses that it is "a structural check... not that any gate actually refuses anyone", so this is a stated limitation rather than a hidden one — but it is the single way the guard can pass on an ungated route, which makes it worth closing.
2. The RED case is not committed
#17344's body describes a manual mutation test — append an ungated route, watch it fail by name, revert. That procedure is not in the suite. Nothing in CI proves
_declares_a_gatestill fires: loosening thecurrent_username check, or a typo in_ROUTE_DECORATORS, would leave the guard green and blind, and only a re-run of the manual steps would catch it.Worth contrasting with #17347, which committed its mutation proof as two tests driving the real
collisions()against a synthetic checkout, plus a mutation of its own assertion. That is the shape this one needs.Acceptance criteria
_declares_a_gatechecks dependency and parameter values, not just presence: adependencies=list satisfies it only if it contains an auth dependency, and acurrent_userparameter only if its default isDepends(get_current_user)(or the equivalent admin gate).dependencies=[Depends(unrelated)]is asserted to FAIL the guard, and one withcurrent_userbound to a non-auth default is asserted to FAIL._MIN_ROUTES_SEENis left intact and still exact — raising it is correct when routes are added, lowering it to clear a red is not.