Repository navigation
fix(analytics): resolve registry-mounted package routers via their own APIRouter prefix (#12945) - #12950
Conversation
…n APIRouter prefix (#12945) #12947 covered registry entries naming a module file; entries naming a package were skipped because applying the registry prefix per-submodule invented endpoints. LLC registers as ('llc.api', '', ...) -- an empty prefix -- while its real paths come from the package router: llc/api/__init__.py: router = APIRouter(prefix='/llc') llc/api/costs.py: router = APIRouter(prefix='/costs') so a submodule serves /api + /llc + /costs. The package-level prefix is now read from its __init__.py and sits between the registry prefix and the submodule prefix _scan_file already applies. Submodules are included only when the package actually mounts routers (include_router present) and the submodule declares one, so helper modules contribute nothing rather than phantom endpoints. Measured on indexed source c5939a51 against app.openapi(): external files 6 -> 28, endpoints 2041 -> 2160, real routes matched 1789 (82.9%) -> 1885 (87.3%), routes missed 370 -> 274, scanned-but-not-real 70 -> 71.
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
|
Reviewed this diff while dogfooding the new 1.
|
Applied Black, isort, and autoflake to match code-quality checks. Triggered by workflow auto-fix-formatting.yml.
…actually mounted (#12956) (#12970) * fix(analytics): resolve nested router subpackages and honour what is actually mounted (#12956) Two defects in _package_router_files, both reported on PR #12950 and both the class #12945 set out to fix -- a submodule mapped to a prefix it is not served under, which invents endpoints that resurface as false "orphaned" findings. 1. rglob descended into nested router subpackages while the "__" filter removed the very __init__.py carrying their prefix, so their modules were emitted under the PARENT's prefix. Now walks one level and recurses, so a nested subpackage resolves under its own prefix. 2. The docstring claimed submodules are included "only when the package actually mounts them via include_router", but the code only checked that the package mounted SOMETHING and then included every router-declaring module. Now the alias must be imported (`from .costs import router as costs_router`) AND passed to include_router. Measured on indexed source c5939a51: external files 28 -> 36, endpoints 2157 -> 2220, real routes matched 1916 -> 1960, and scanned-but-not-real UNCHANGED at 38 -- the tightening gained routes without inventing any. Three #12950 fixtures mounted an alias without importing it, which no real registry does (llc/api/__init__.py has 30 such imports). Corrected rather than loosening the check to accommodate them. * Auto-fix: code formatting Applied Black, isort, and autoflake to match code-quality checks. Triggered by workflow auto-fix-formatting.yml. --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Closes #12945.
Thinking Path
#12947 handled registry entries naming a module file and deliberately skipped those naming a package, because my first attempt at packages made things measurably worse: applying the registry prefix to each submodule produced 182 endpoints of which 4 were real, pushing scanned-but-not-real from 70 to 209.
Reading how LLC is actually mounted explains why, and what the correct rule is:
So a submodule serves
/api+/llc(package router) +/costs(submodule router) + the route path. There is a third prefix level the module-file case does not have, and it lives in the package's own__init__.py— nowhere near the registry entry that names it. Confirmed the sub-routers are included with no additional prefix, so the package router is the only intermediate level._scan_filealready applies a file's ownAPIRouter(prefix=…), so only the package-level part belongs in the prefix map.Two guards against the failure mode that made the first attempt worse:
include_routerpresent in__init__.py), otherwise the entry is not a router package;APIRouter, so helper modules contribute nothing rather than endpoints at a guessed path.What Changed
api/codebase_analytics/api_endpoint_scanner.py:_registry_router_files()now dispatches a package entry to_package_router_files()._package_router_files()reads the package's__init__.py, takes its router prefix, and maps each router-declaring submodule toregistry_prefix + package_prefix.api_endpoint_scanner_test.py: the placeholder "packages are skipped" test is replaced with four real ones — the prefix coming from the package's own router, the full served path end-to-end (/api/llc/costs/by-agent), helper modules without a router being skipped, and a package that mounts nothing being skipped.Verification
Measured on the live install's indexed source
c5939a51-…, read-only, againstapp.openapi()(2159 unique normalized paths):api/+96 real routes for +1 spurious. 119 LLC endpoints discovered, 96 of them confirmed present in
app.openapi().End-user metric across the whole thread, on the same source:
The genuine count holding at exactly 20 across all three fixes is the useful signal: the number of real drift findings is stable, and everything removed so far was scan gap rather than real drift.
What remains (not this issue)
274 real routes are still unmatched and 270 of the 290 unique missing findings remain false positives. That residue is no longer about registry-mounted modules — all 7 are now covered — so it belongs to a different gap (routes reached by neither the
api/walk nor a registry entry, e.g. nestedinclude_routerchains). Worth a fresh issue with these numbers as the baseline rather than widening this one.Model Used
claude-opus-5