Skip to content

fix(routing): total_pages=0 document passes routing validation and corrupts token-reduction estimate #279

Closed

Description

@piyushmakhija28

Summary

route_query() checks fallback_only on each document but does not check total_pages. A document with total_pages=0 (race condition between ingest completion and route call, or a data integrity gap) passes all guards, enters routing, and returns estimated_token_reduction='0%' — which is misleading. Worse, the router module may perform integer arithmetic with total_pages=0 causing division-by-zero or negative page-range comparisons.

Root Cause

backend/app/api/routing.py around line 99:

total_pages = sum(d.total_pages for d in docs)   # can be 0
# ...
if any(d.fallback_only for d in docs):
    raise ...   # only fallback_only is checked, not total_pages=0

estimate_token_reduction(total_pages=0, ...) returns '0%'. The router module receives total_pages=0 and may compute section_coverage = selected_pages / total_pages → ZeroDivisionError.

Failure Scenario

  1. Tenant uploads a PDF. Ingest starts but has not yet written total_pages to Postgres.
  2. Tenant immediately routes a query against the document (race condition).
  3. store.get_document(doc_id) returns DocumentRecord(total_pages=0, fallback_only=False).
  4. fallback_only=False passes the guard.
  5. total_pages=0 is passed to the router module.
  6. Router computes coverage_ratio = len(selected_sections) / 0 → ZeroDivisionError (unhandled 500).
  7. Or: returns estimated_token_reduction='0%' — caller believes all tokens are used, no reduction.

Proposed Fix

Add an explicit guard for total_pages=0:

for doc in docs:
    if doc.total_pages == 0:
        raise HTTPException(
            status_code=409,
            detail={
                "code": "DOCUMENT_NOT_READY",
                "document_id": doc.doc_id,
                "message": "Document is still being indexed. Retry in a few seconds.",
            }
        )
    if doc.fallback_only:
        raise ...

This gives callers a clear, retryable signal instead of a cryptic 500.

Activity

  1. piyushmakhija28 commented on Jun 21, 2026

    @piyushmakhija28
    ContributorAuthor

    Fixed in PR #280 (Sprint R3/R4: fix(sprint-r3): root-cause fixes for all 39 bugs from code review round 3 + Sprint R4 round 4)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcode-reviewFound via automated code reviewpriority: mediumFunctional error with workaround

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions