Repository navigation
guard(auth): the ungated control must name a router scheduled to be fixed, so every fix breaks it #16242
Description
Activity
What landed today (#16370, afccf73)
Checked against
origin/mainat b688b3e. #16370 referenced this issue (Refs); it did not close it. Its body marks this issue as separate scope.AC Evidence on origin/mainStatus 1. The "genuinely nothing" control classifies a synthetic tmp_pathrouterrepo_tests/router_auth_coverage_test.py:93is still("api.knowledge_search", "UNGATED", "genuinely nothing"), a production module listed inKNOWN_UNGATED(:35-41)❌ 2. The synthetic subject has no gate and no marker No synthetic subject exists in that test ❌ 3. The other four controls are checked, and the test says why they are stable The other rows carry no such note. test_one_control_per_gating_mechanism's docstring says only "If a control's verdict changes, the DETECTOR broke"❌ 4. Gating every KNOWN_UNGATEDrouter leaves the control list alone:93fails as soon asapi.knowledge_searchis gated❌ What landed: #16370 added the shape AC1 asks for to the sibling config-registry guard:
repo_tests/config_router_auth_coverage_test.py:132test_a_planted_ungated_feature_router_is_caught(tmp_path)and:144for the gated contrast. The same pattern can be ported torouter_auth_coverage_test.py.What remains: all four ACs.
The outcome this issue predicted has already happened. Verified against
origin/mainduring a closed-state duplicate pass for the v0.10.0 triage (#17863).repo_tests/router_auth_coverage_test.py:81-91now parametrises five controls:("api.agent_org", "GATED", "router-level APIRouter(dependencies=[...])") ("api.system", "GATED", "per-route check_admin_permission") ("api.websockets", "GATED", "inline in the handler body") ("api.knowledge_search", "GATED", "router-level APIRouter(dependencies=[...]) (#15745)") ("api.chat_embed", "UNGATED-BY-DESIGN", "documented in the module docstring")Four GATED, one UNGATED-BY-DESIGN, and no plain-ungated control.
api.knowledge_searchwas the ungated one and is now gated — the row itself cites #15745 for that change.Why that matters, in the guard's own words. Its docstring at
:92-96states the design: "If a control's verdict changes, the DETECTOR broke -- not the tree. Each row is a different mechanism. Two controls of the same shape cannot tell you the detector covers a shape it has never seen."By that logic the detector's ability to report a plain ungated router is now untested.
UNGATED-BY-DESIGNis a different verdict value exercising a different path — the documented-exception path, not the genuinely-unprotected one. So if a router silently loses its gate tomorrow, nothing in this control table demonstrates the detector would say so.That is precisely the failure mode this issue was opened to prevent, and it is live rather than hypothetical.
Not proposing a fix here, since the remedy is this issue's own subject: a synthetic control that is ungated on purpose and cannot be "fixed" out from under the guard. Worth noting that a
git grepfortmp_pathandsyntheticin this file and inrepo_tests/router_auth_enumerator.pyreturns nothing, so no synthetic control exists yet.One attribution note: a parallel reading of this attributed the gating of
api.knowledge_searchto #16507. The control row itself cites #15745. I have not established which is correct and am reporting what the file says.
Problem
repo_tests/router_auth_coverage_test.pyproves its detector works by asserting one control router per gating mechanism, including one that must classify as UNGATED — genuinely nothing. That control has to name a real registered router with no gate.Every such router is, by definition, on
KNOWN_UNGATEDand scheduled to be fixed. So the control is structurally guaranteed to break each time the router it names gets its gate. That has now happened once:api.redisapi.redisapi.redis (genuinely nothing) is GATED, expected UNGATED. Evidence: router-level: check_admin_permissionIt was re-pointed to
api.knowledge_search, which is itself scheduled for tenant scoping under #15745 AC4. The same failure will recur there, and again for every successor, until the last ungated router is fixed — at which point no real router can serve as the control at all, and the test either fails permanently or someone deletes the case.The breakage is correct behaviour — a control that stops matching is exactly what a control is for. The defect is that the control's subject is drawn from the population the work is trying to empty.
Scope
Give the UNGATED control a subject that stays ungated regardless of how the real tree changes.
Acceptance criteria
tmp_pathand fed to the enumerator — not a production module onKNOWN_UNGATED.KNOWN_UNGATEDno longer requires touching the control list.Notes
The pattern generalises beyond this file: a positive control for "the bad state" cannot be drawn from the set of things you are fixing. Drawing it from there makes every fix look like a guard failure, which teaches people to edit the control rather than read it.
Found while landing the first #15745 fix. Related: #15745.