Skip to content

tech-debt(analytics): endpoint scan still misses 274 real routes (93.1% of missing findings are false) after the registry-module fixes #12953

Description

@mrveiss

Follow-on from #12853 → #12944 → #12946 → #12945. Those closed the known scan gaps; this tracks the measured residue, with a concrete baseline instead of a guess.

State after the three fixes

Measured read-only against indexed source c5939a51-… on the local test install, comparing the scan to app.openapi() (2159 unique normalized paths):

before #12943 #12943 #12947 #12950
backend endpoints 0 1986 2041 2160
real routes matched 0 1736 (80.4%) 1789 (82.9%) 1885 (87.3%)
real routes missed 2159 423 370 274
scanned but not real — 70 70 71
missing findings ~2400 488 435 330
false positives among them ~97% 95.4% 94.8% 93.1%
genuine findings — 20 20 20

The genuine count holding at exactly 20 across all three fixes is the signal worth keeping: real drift is stable, and every finding removed so far was scan gap, not drift.

What is left

274 real routes still unmatched, and 270 of the 290 unique missing findings are routes that genuinely exist. This is no longer about registry-mounted modules — all 7 are now scanned (6 module-form in #12947, the llc.api package in #12950).

Remaining candidates, in rough order of expected yield:

  1. Nested include_router chains. _scan_include_router_patterns() handles api/ subdirectories, and fix(analytics): resolve registry-mounted package routers via their own APIRouter prefix (#12945) #12950 handles one package level. A router included two levels deep still resolves to the wrong prefix or none.
  2. Routers mounted outside the registries entirely — e.g. app.include_router(...) in app_factory or a lifespan hook, which no registry file names.
  3. Data-driven mounts where the prefix is computed rather than a literal, so the regex-based prefix extraction sees nothing.

71 scanned-but-not-real paths also remain — prefix reconstruction is still imperfect somewhere; that number barely moved across all three fixes (70 → 70 → 71), suggesting a single localized cause rather than a spread.

Reproduce

from api.codebase_analytics.api_endpoint_scanner import BackendEndpointScanner
scanned = {norm(e.path) for e in BackendEndpointScanner(Path(src_root)).scan_all_endpoints()}
real    = {norm(p) for p in json.load(open("openapi.json"))["paths"]}
print(len(real - scanned), "missed;", len(scanned - real), "spurious")

with norm() collapsing {...} to {p} and stripping a trailing /.

Done when

  • The missed count drops well below 274, or each remaining cluster is explained and shown to be unreachable by static analysis (dynamic mounts) rather than simply unimplemented.
  • The 71 spurious paths are diagnosed to their cause.

Note

Do not close this by baselining. #12894 showed what suppression does to this exact family of findings — it hid 29 entries behind a label that read as drift and was not.

Activity

  1. mrveiss commented on Jul 29, 2026

    @mrveiss
    OwnerAuthor

    Diagnosed the 71 scanned-but-not-real paths. My hypothesis in the issue body was wrong — I guessed one localized cause because the count barely moved (70 → 70 → 71) across three fixes. It is actually several distinct classes; the count held steady because each fix addressed missed routes, not spurious ones.

    Breakdown by source, on indexed source c5939a51-…:

    1. Router prefix never resolved — path emitted without its namespace. The largest class.

    /api-calls          <- api/codebase_analytics/endpoints/api_endpoints.py
    /api-endpoints      <- api/codebase_analytics/endpoints/api_endpoints.py
    /api/click          <- api/vnc_manager.py
    /api/clipboard      <- api/vnc_manager.py
    /api/cache/evict    <- api/system.py
    

    Note /api-calls is not even under /api/ — the module prefix resolved to empty and the route path was concatenated directly, so these cannot match anything. Top namespaces among the 71: users 8, session 7, knowledge 5, macro 4, organizations 4, teams 4.

    2. Test files contribute endpoints. Small but a genuine correctness defect:

    endpoints from test files       : 3
      spurious (not real)           : 2
      matching a real route         : 0
    

    from api/marketplace_422_test.py, api/schemas_analytics_date_range_test.py, api/user_management/test_tenant_context_resolution.py. scan_all_endpoints() already skips __* and archive, but not *_test.py / test_* / tests/. A route defined in a test is not an endpoint, and it pollutes both directions — it inflates spurious counts and can surface as a phantom "orphaned" finding telling someone to wire or delete something that does not exist.

    3. Genuinely-mounted-elsewhere routes, e.g. /api/chat/completions from api/openai_compat.py and /api/admin/realtime/bundle/me from api/voice_bundle_admin.py — these need checking individually against how they are actually mounted before assuming they are wrong.

    Suggested order

    1. Exclude test files from the scan — smallest, unambiguous, no judgement needed.
    2. Fix the empty-prefix class — the /api-calls shape proves the module prefix resolved to "" rather than to a wrong value, so _get_module_prefix() is falling through to its default for these modules. That likely overlaps with the 274 missed routes: a module whose prefix does not resolve emits its routes at the wrong path, which shows up as one spurious entry and one missed entry. Fixing it should move both counts at once.
    3. Triage class 3 case by case.

    That overlap is the useful insight: spurious and missed are not independent residues, so the 274 and the 71 are partly the same defect seen from two sides.

  2. mrveiss commented on Jul 29, 2026

    @mrveiss
    OwnerAuthor

    Root cause of the empty-prefix class found — and it is much larger than the 71 spurious paths suggested.

    _module_prefix_map has no entry at all for the affected modules:

    vnc_manager      prefix_map=None
    api_endpoints    prefix_map=None
    openai_compat    prefix_map=None
    system           prefix_map='/api/system'     <- one that does resolve
    

    So _get_module_prefix() falls through to its /api default, which is why /api/click and /api-calls come out without their namespace.

    Why the map is empty for them. core_routers.py registers routers in two steps, and the parser only understands the second half of it:

    from api.vnc_manager import router as vnc_router     # line 104  -- module -> variable
    ...
    (vnc_router, "/vnc", ["vnc"], "vnc"),                # line 418  -- variable -> prefix

    _parse_config_tuple_registry matches tuples whose first element is a quoted module path (("services.advanced_workflow.routes", "/advanced-workflow", …)). Here the first element is a bare identifier. _apply_dynamic_router_pattern does handle (router_var, "/prefix", …) tuples, but nothing links vnc_router back to api.vnc_manager, so the prefix is never attributed to a module.

    Scale — this is not a long tail:

    core_routers.py:  90  routers registered as `from api.X import router as Y_router`
    core_routers.py:   0  config-tuple entries with a quoted module path
    

    Ninety modules whose prefix never resolves. That is consistent with the residue being large in both directions, and it confirms the overlap I flagged earlier: a module whose prefix resolves to nothing emits each of its routes at the wrong path, producing one spurious entry and one missed entry per route. So a large share of the remaining 274 missed and 69 spurious should be the same 90 modules.

    Fix shape: in _collect_router_prefixes(), parse the from api.<module> import router as <var> aliases in each registry file, then when _apply_dynamic_router_pattern sees (<var>, "/prefix", …), attribute that prefix to <module> in _module_prefix_map. Both halves already exist — only the link between them is missing.

    Expected payoff: this should be the single biggest remaining move on both counts, and unlike the earlier package work it carries little risk of inventing endpoints, because it corrects the prefix of routes the scanner already finds rather than adding new ones. Verify by confirming real routes matched rises and scanned but not real falls together.

    Test-module exclusion (the other class from my previous comment) is split to #12957 and delivered in PR #12958.

  3. mrveiss commented on Jul 29, 2026

    @mrveiss
    OwnerAuthor

    PR #12961 merged as df0df5002 — the empty-prefix class is fixed, and my "90 modules" claim above was wrong; correcting it for the record.

    The parser does derive a module from the alias by stripping _router, and that guess is right for 88 of the 90 entries. The defect is narrower: where the alias does not mirror its module, the guess is wrong twice —

    from api.vnc_manager import router as vnc_router
    (vnc_router, "/vnc", ["vnc"], "vnc"),

    /vnc went to api.vnc (a different module that also exists, so the error is invisible) while api.vnc_manager got nothing and emitted /api/click, /api/clipboard.

    Two modules affected — vnc_manager, overseer_handlers — carrying 31 routes:

    before after
    real routes matched 1885 1916 (+31)
    scanned but not real 69 38 (−31)
    real routes missed 274 243 (−31)
    missing findings 330 290
    false positives 270 239
    orphaned findings 290 258

    The +31 / −31 / −31 symmetry confirms the overlap predicted earlier: spurious and missed were the same defect from two sides. This is also the first change in the series to move the spurious count, which had sat at 70/70/71/69 throughout.

    A detour worth recording: I first applied the fix to _apply_dynamic_router_pattern and nothing moved. core_routers.py is parsed by _parse_router_registry (simple-tuple format), so the dynamic path never sees the file where all 90 aliases live. Both now carry the fix; only one mattered.

    Remaining on this issue

    243 real routes missed, 38 spurious. Different cause: modules registered in no tuple at all — api_endpoints and openai_compat both resolve to None for this reason, not via a bad alias. Next step is to establish how those modules reach the app (nested include_router chains, or mounts outside the registries entirely) before writing anything.

  4. mrveiss commented on Jul 29, 2026

    @mrveiss
    OwnerAuthor

    Traced the two None-prefix modules from my earlier comment. They have different causes, and the second means part of the remaining residue is real signal rather than scanner error.

    1. api_endpoints — nested include_router via relative imports. It is mounted one level below the registry:

    # api/codebase_analytics/router.py
    from .endpoints import api_endpoints        # 6 of these
    router.include_router(api_endpoints.router) # 17 include_router calls total

    _scan_include_router_patterns() handles include_router chains, but not this from .endpoints import mod + include_router(mod.router) shape. Scope is contained: codebase_analytics/router.py is the only api/*/router.py using it, with 17 sub-routers behind it. That makes it a bounded, well-defined next fix.

    2. openai_compat — appears to have no mount site at all. Searching the whole backend finds no include_router for it and no registry entry; the only references outside its own module are in conftest.py. If that holds, /api/chat/completions is not served, and the scanner reporting it as "scanned but not in app.openapi()" is correct — a router that exists and is never mounted.

    That distinction matters for how this issue should be closed. The remaining 38 spurious are not all scanner defects:

    • some are prefix-resolution gaps (fixable, e.g. the nested chains above);
    • some are genuine unmounted routers — real findings the audit's own "UNMOUNTED ROUTER MODULES" section exists to report.

    Driving the spurious count to zero would therefore be the wrong target: it would mean suppressing true findings. The right target is to fix the resolution gaps and then confirm each survivor is a genuinely dead router, at which point it belongs in the unmounted-router report rather than counted as scan error.

    Same caution applies to the 243 missed: some will be dynamically-registered routes no static scan can see. Worth establishing that floor before treating the remainder as a defect.

    Suggested next step: extend _scan_include_router_patterns() to follow relative from .<pkg> import <mod> + include_router(<mod>.router), verify against the 17 sub-routers behind codebase_analytics/router.py, then re-measure and triage what is left.

  5. mrveiss commented on Jul 29, 2026

    @mrveiss
    OwnerAuthor

    Measured the nested-import fix before writing it, and it is not worth doing as scoped. Correcting my previous comment.

    I implied 17 sub-routers were behind the unresolved codebase_analytics/router.py chain. In practice the existing subdirectory special-case (#1469) already resolves nearly all of them:

    codebase_analytics endpoints scanned : 84
      resolving correctly                : 82
      wrong                              :  2   -> /api-calls, /api-endpoints
    

    So following relative from .endpoints import mod chains would recover 2 paths, not a meaningful share of the 38 spurious. Not worth a PR against the rest of the backlog.

    The larger number nearby is unrelated to prefixes:

    real codebase/analytics routes in app.openapi() : 228
      missed by the scanner                          : 50
    

    Those 50 are missed routes, not misprefixed ones — so they belong to whatever mounts them, and diagnosing that is the useful next step rather than the import-chain work.

    Pattern worth flagging on this issue: three scope estimates on this thread have now come in high — "419 routes behind 7 modules" (actually ~53), "90 modules with unresolved prefixes" (actually 2), and "17 sub-routers" (actually 2). Each time the cheap measurement contradicted the reasoning-from-code-shape. Anyone continuing here should measure the delta on the live indexed source before implementing; it costs one command and has repeatedly changed the decision.

    Given that, the highest-value remaining work on this issue is triage, not code: establish how many of the 243 missed are dynamically registered (a floor no static scan can cross) and how many of the 38 spurious are genuinely unmounted routers (true findings). Only what survives both is a defect worth fixing.

  6. modified the milestones: Backlog, v0.12.0 on Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions