Skip to content

fix(analytics): scan registry-mounted router modules outside api/ (#12946) - #12947

Merged
mrveiss merged 2 commits into
Dev_new_guifrom
issue-12945
Jul 29, 2026
Merged

mrveiss merged 2 commits into
Dev_new_guifrom
issue-12945

Conversation

@mrveiss

@mrveiss mrveiss commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Closes #12946. Refs #12945, which stays open for the package case.

Thinking Path

#12945 measured that 419 of 439 unique "missing endpoint" findings (95.4%) are routes that genuinely exist, and that all 419 are scan gaps rather than matcher bugs. The cause is that scan_all_endpoints() walks backend_dir / "api" only, while the registries mount seven router modules from sibling packages.

Feeding those files into the existing loop is not enough on its own. _get_module_prefix() resolves a file by its path relative to project_root and then by py_file.stem — and the stem of services/advanced_workflow/routes.py is routes, which matches nothing. The routes would have been discovered under /api instead of /api/advanced-workflow, i.e. still wrong, just wrong in a new way. The registry prefix is therefore carried to the resolved file explicitly.

The package case turned out to be actively harmful, and that shaped the scope. My first cut expanded a registry entry naming a package by walking its *.py files and applying the package's prefix to each. Measured against the live instance, that made things worse:

                        files-and-packages   files-only
scanned NOT in openapi          209              70

LLC registers as ("llc.api", "", ["llc"], "llc") — an empty prefix — because its real /api/llc/* paths come from include_router calls inside the package's own __init__.py. Applying that per-submodule produced /api/costs/by-agent-model instead of /api/llc/costs/by-agent-model: 182 endpoints of which 4 were real. Inventing 178 endpoints is worse than missing them, since they come back as phantom "orphaned" findings — the same class of noise this whole thread is trying to remove. So this change is limited to module files, where the registry prefix is unambiguous, and packages are left to #12945 with the include_router walk they need.

What Changed

api/codebase_analytics/api_endpoint_scanner.py:

  • _registry_router_files() resolves registry module paths (dotted, non-api.) against the backend directory and returns {file: prefix} for those that resolve to a module file.
  • _external_router_prefixes is consulted first in _get_module_prefix(), so the registered prefix reaches the endpoint path.
  • scan_all_endpoints() scans those files alongside api/**, and logs how many lie outside api/.
  • _get_module_prefix() no longer raises on a file outside project_root — relative_to was unguarded, and _scan_file swallows exceptions, so such a file would have silently contributed zero endpoints.

api_endpoint_scanner_test.py: 5 tests — external module resolved with its prefix, the prefix reaching the scanned path end-to-end, api.* entries not duplicated, package entries skipped (with the LLC numbers as the rationale), and a missing module file not breaking the scan.

Verification

$ python3 -m pytest api/codebase_analytics/api_endpoint_scanner_test.py -q
16 passed          (11 pre-existing + 5 new)

$ python3 -m pytest api/codebase_analytics/ -q
230 passed, 11 skipped in 4.35s        (was 225 passed / 11 skipped)

$ python3 -m flake8 …/api_endpoint_scanner.py …/api_endpoint_scanner_test.py
(clean)

Measured on the live install's indexed source c5939a51-…, read-only, against the authoritative app.openapi() table (2159 unique normalized paths):

before after
router files scanned outside api/ 0 6
endpoints found 1986 2041
real routes matched 1736 (80.4%) 1789 (82.9%)
real routes missed 423 370
scanned but not real 70 70
missing findings 488 435
coverage 86.5% 86.7%

53 real routes recovered with no new spurious endpoints — the scanned-but-not-real count is unchanged, which is the guard against the failure mode described above.

What remains (#12945)

The false-positive share moves only 95.4% → 94.8%, because the bulk of the remaining 366 sits behind the package case — LLC alone is ~182 routes. Closing that needs the include_router walk to resolve per-submodule prefixes, which is a distinct piece of work and is why this PR does not claim #12945.

Model Used

claude-opus-5

…2946)

The scan walked backend_dir/'api' only, but the registries also mount routers
from sibling packages -- services.advanced_workflow.routes, routers.* and
friends. Their routes were never discovered, so every frontend call to one was
reported as a missing endpoint.

The prefix has to be carried explicitly: _get_module_prefix() resolves a file
via its path relative to project_root and then py_file.stem, and the stem of
services/advanced_workflow/routes.py is 'routes', which matches nothing.
Feeding these files into the existing loop alone would have found the routes
under /api instead of /api/advanced-workflow.

Limited to module files. A registry entry naming a package cannot have its
prefix applied to the modules inside it: llc registers as ('llc.api', '', ...)
and its real /api/llc/* paths come from include_router calls in the package's
own __init__.py. Applying that empty prefix per-submodule produced /api/costs/...
instead of /api/llc/costs/..., inventing 182 endpoints of which 4 were real --
worse than missing them, since they resurface as phantom orphaned findings.
Packages are left to #12945.

Measured on indexed source c5939a51 against app.openapi(): 6 external files,
endpoints 1986 -> 2041, real routes matched 1736 -> 1789, routes missed
423 -> 370, and scanned-but-not-real held at exactly 70.
@github-actions

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No hardcoded values detected that have SSOT config equivalents!

Applied Black, isort, and autoflake to match code-quality checks.
Triggered by workflow auto-fix-formatting.yml.
@mrveiss
mrveiss merged commit 95cc9d7 into Dev_new_gui Jul 29, 2026
39 checks passed
@mrveiss
mrveiss deleted the issue-12945 branch July 29, 2026 05:56
mrveiss added a commit that referenced this pull request Jul 29, 2026
…n APIRouter prefix (#12945) (#12950)

* fix(analytics): resolve registry-mounted package routers via their own 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.

* 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] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant