Domain: web-service request router (route specificity)

Issue:
After we reorganized how routes are registered, some API endpoints return the wrong resource: requesting the daily report gives back the overall summary instead. Most endpoints behave normally, and the broken one worked before the reorganization — nothing about the endpoint's own handler changed. Expected behavior: a request is served by the handler of the most specific route that covers its path, regardless of the order routes were registered in, as the routing API documents.

Repository files:
content: # Present so pytest adds the repository root to sys.path, making `import src`
# resolve from the test suite.

path: conftest.py

content: # Pure standard library unless the environment image pre-bakes wheels.

path: requirements.lock

path: src/__init__.py

content: """Application wiring."""

from . import handlers
from .config import load_settings
from .errors import ApiError
from .http import not_found
from .middleware import DEFAULT_CHAIN
from .router import Router
from .utils import normalize_path


class App:
    """Dispatches requests through middleware to routed handlers."""

    def __init__(self, router, settings, chain=DEFAULT_CHAIN):
        self.router = router
        self.settings = settings
        self.chain = chain

    def handle(self, request):
        """Serve one request."""
        for middleware in self.chain:
            response = middleware(request, self.settings)
            if response is not None:
                return response
        route = self.router.resolve(normalize_path(request.path))
        if route is None:
            return not_found(request.path)
        try:
            return route.handler(request, self.settings)
        except ApiError as exc:
            return exc.to_response()


def build_app(overrides=None):
    """Construct the demo API with its full route table."""
    settings = load_settings(overrides)
    router = Router()
    router.add("/", handlers.health.index)
    router.add("/health", handlers.health.health)
    router.add("/api/users/export", handlers.users.export_users)
    router.add("/api/users", handlers.users.list_users)
    router.add("/api/orders/recent", handlers.orders.recent_orders)
    router.add("/api/orders", handlers.orders.list_orders)
    router.add("/api/reports", handlers.reports.summary)
    router.add("/api/reports/daily", handlers.reports.daily)
    router.add("/api/search", handlers.search.search)
    router.add("/api", handlers.health.api_index)
    router.add("/admin/users", handlers.admin.manage_users)
    router.add("/admin", handlers.admin.dashboard)
    return App(router, settings)

path: src/app.py

content: """Token handling for privileged endpoints."""

from .errors import AuthError

TOKEN_HEADER = "X-Token"


def extract_token(request):
    """The bearer token supplied with a request, or None."""
    return request.headers.get(TOKEN_HEADER)


def require_admin(request, settings):
    """Raise AuthError unless the request carries the admin token."""
    token = extract_token(request)
    if token is None:
        raise AuthError("missing {} header".format(TOKEN_HEADER))
    if token != settings.admin_token:
        raise AuthError("invalid token")


def is_admin(request, settings):
    """Whether the request carries the admin token."""
    try:
        require_admin(request, settings)
    except AuthError:
        return False
    return True

path: src/auth.py

content: """Application settings."""

from dataclasses import dataclass

DEFAULTS = {
    "service_name": "demo-api",
    "admin_token": "sesame",
    "page_size": 10,
    "max_page_size": 50,
    "request_id_header": "X-Request-Id",
}


@dataclass(frozen=True)
class Settings:
    """Immutable runtime settings."""

    service_name: str
    admin_token: str
    page_size: int
    max_page_size: int
    request_id_header: str


def load_settings(overrides=None):
    """Build Settings from the defaults plus explicit overrides."""
    merged = dict(DEFAULTS)
    for key, value in (overrides or {}).items():
        if key not in DEFAULTS:
            raise KeyError("unknown setting: {}".format(key))
        merged[key] = value
    return Settings(**merged)

path: src/config.py

content: """Application error types and their HTTP mapping."""

from .http import Response


class ApiError(Exception):
    """Base class for errors that map to an HTTP response."""

    status = 500

    def to_response(self):
        return Response(self.status, "error: {}".format(self))


class ValidationError(ApiError):
    """A request failed validation."""

    status = 400


class AuthError(ApiError):
    """A request lacked valid credentials."""

    status = 401


class UnknownResourceError(ApiError):
    """A referenced resource does not exist."""

    status = 404

path: src/errors.py

content: """Endpoint handlers, grouped by resource."""

from . import admin, health, orders, reports, search, users  # noqa: F401

path: src/handlers/__init__.py

content: """Administrative endpoints (token-gated by middleware)."""

from ..http import ok
from ..render import render_list, render_record
from .users import USERS


def dashboard(request, settings):
    """Operational overview for administrators."""
    return ok(
        render_record(
            "admin dashboard",
            {"service": settings.service_name, "users": len(USERS)},
        )
    )


def manage_users(request, settings):
    """User management view."""
    labels = ["{} <{}>".format(name, email) for _, name, email in USERS]
    return ok(render_list("managed users", labels))

path: src/handlers/admin.py

content: """Service-level endpoints."""

from ..http import ok


def index(request, settings):
    """Service banner."""
    return ok("welcome to {}".format(settings.service_name))


def health(request, settings):
    """Liveness check."""
    return ok("status: healthy")


def api_index(request, settings):
    """Top-level API directory."""
    return ok(
        "api index\n"
        "- /api/users\n"
        "- /api/orders\n"
        "- /api/reports\n"
        "- /api/search"
    )

path: src/handlers/health.py

content: """Order endpoints."""

from ..http import ok
from ..pagination import page_params, paginate
from ..render import render_list
from ..validation import validate_limit

ORDERS = [
    ("o1", "ada", 120),
    ("o2", "grace", 80),
    ("o3", "ada", 45),
    ("o4", "alan", 310),
    ("o5", "barbara", 95),
    ("o6", "grace", 60),
    ("o7", "donald", 150),
]


def list_orders(request, settings):
    """Paginated order listing."""
    page, size = page_params(request.query, settings)
    labels = ["{} by {} ({})".format(oid, who, total) for oid, who, total in ORDERS]
    return ok(render_list("orders", paginate(labels, page, size)))


def recent_orders(request, settings):
    """The most recently placed orders, newest first."""
    limit = validate_limit(request.query, settings.max_page_size)
    labels = ["{} by {}".format(oid, who) for oid, who, _ in reversed(ORDERS)]
    return ok(render_list("recent orders", labels[:limit]))

path: src/handlers/orders.py

content: """Reporting endpoints."""

from ..http import ok
from ..render import render_record
from .orders import ORDERS
from .users import USERS


def summary(request, settings):
    """Roll-up counts across the service."""
    return ok(
        render_record(
            "summary report",
            {
                "users": len(USERS),
                "orders": len(ORDERS),
                "revenue": sum(total for _, _, total in ORDERS),
            },
        )
    )


def daily(request, settings):
    """Today's activity report."""
    latest_id, latest_who, latest_total = ORDERS[-1]
    return ok(
        render_record(
            "daily report",
            {
                "orders_today": 2,
                "latest_order": latest_id,
                "latest_customer": latest_who,
                "latest_total": latest_total,
            },
        )
    )

path: src/handlers/reports.py

content: """Cross-resource search."""

from ..errors import ValidationError
from ..http import ok
from ..render import render_list
from .orders import ORDERS
from .users import USERS


def search(request, settings):
    """Substring search over user names and order ids."""
    term = request.query.get("q", "").strip().lower()
    if not term:
        raise ValidationError("q is required")
    hits = ["user:{}".format(name) for _, name, _ in USERS if term in name.lower()]
    hits.extend("order:{}".format(oid) for oid, _, _ in ORDERS if term in oid.lower())
    return ok(render_list("results", hits))

path: src/handlers/search.py

content: """User endpoints."""

from ..http import ok
from ..pagination import page_params, paginate
from ..render import render_csv, render_list
from ..validation import validate_flag

USERS = [
    ("u1", "ada", "ada@example.test"),
    ("u2", "grace", "grace@example.test"),
    ("u3", "edsger", "edsger@example.test"),
    ("u4", "alan", "alan@example.test"),
    ("u5", "barbara", "barbara@example.test"),
    ("u6", "donald", "donald@example.test"),
]


def list_users(request, settings):
    """Paginated user listing."""
    page, size = page_params(request.query, settings)
    names = [name for _, name, _ in USERS]
    return ok(render_list("users", paginate(names, page, size)))


def export_users(request, settings):
    """CSV export of every user."""
    include_email = validate_flag(request.query, "emails")
    rows = [
        (user_id, name) + ((email,) if include_email else ())
        for user_id, name, email in USERS
    ]
    return ok(render_csv([("id", "name") + (("email",) if include_email else ())] + rows))

path: src/handlers/users.py

content: """HTTP request/response primitives."""

from dataclasses import dataclass, field


@dataclass(frozen=True)
class Request:
    """An incoming request."""

    method: str
    path: str
    query: dict = field(default_factory=dict)
    headers: dict = field(default_factory=dict)
    body: str = ""


@dataclass
class Response:
    """An outgoing response."""

    status: int
    body: str
    headers: dict = field(default_factory=dict)


def ok(body, headers=None):
    """A 200 response."""
    return Response(200, body, dict(headers or {}))


def created(body):
    """A 201 response."""
    return Response(201, body)


def bad_request(message):
    """A 400 response."""
    return Response(400, "bad request: {}".format(message))


def unauthorized(message):
    """A 401 response."""
    return Response(401, "unauthorized: {}".format(message))


def not_found(path):
    """A 404 response."""
    return Response(404, "no route for {}".format(path))

path: src/http.py

content: """Request middleware.

A middleware is a callable (request, settings) -> Response | None. Returning
a Response short-circuits the request; returning None passes it on.
"""

import itertools

from .auth import require_admin
from .errors import AuthError

_request_counter = itertools.count(1)


def request_id(request, settings):
    """Stamp each request with a sequential id header (never short-circuits)."""
    request.headers.setdefault(settings.request_id_header, str(next(_request_counter)))
    return None


def admin_gate(request, settings):
    """Reject /admin requests that lack the admin token."""
    if request.path == "/admin" or request.path.startswith("/admin/"):
        try:
            require_admin(request, settings)
        except AuthError as exc:
            return exc.to_response()
    return None


def method_guard(request, settings):
    """Only GET is supported by this service."""
    if request.method != "GET":
        from .http import bad_request

        return bad_request("only GET is supported")
    return None

DEFAULT_CHAIN = (request_id, method_guard, admin_gate)

path: src/middleware.py

content: """Pagination of list results."""

from .utils import clamp, parse_int


def page_params(query, settings):
    """Extract (page, size) from a query, applying defaults and bounds."""
    page = max(1, parse_int(query.get("page"), 1))
    size = clamp(
        parse_int(query.get("size"), settings.page_size), 1, settings.max_page_size
    )
    return page, size


def paginate(items, page, size):
    """The slice of items for a 1-indexed page."""
    start = (page - 1) * size
    return items[start : start + size]


def page_count(items, size):
    """Number of pages needed for items at the given page size."""
    if not items:
        return 0
    return (len(items) + size - 1) // size

path: src/pagination.py

content: """Plain-text rendering of API payloads."""


def render_list(name, items):
    """Render a named collection, one item per line."""
    lines = ["{} ({})".format(name, len(items))]
    lines.extend("- {}".format(item) for item in items)
    return "\n".join(lines)


def render_record(name, fields):
    """Render a single record as 'key=value' lines under a heading."""
    lines = [name]
    lines.extend("{}={}".format(key, fields[key]) for key in sorted(fields))
    return "\n".join(lines)


def render_csv(rows):
    """Render rows of values as comma-separated lines."""
    return "\n".join(",".join(str(value) for value in row) for row in rows)

path: src/render.py

content: """Prefix routing."""

from dataclasses import dataclass

from .utils import normalize_path


@dataclass(frozen=True)
class Route:
    """A registered route."""

    pattern: str
    handler: object
    name: str


def matches(pattern, path):
    """Whether pattern covers path at a segment boundary."""
    if pattern == path:
        return True
    if pattern == "/":
        return False
    return path.startswith(pattern + "/")


class Router:
    """Maps request paths to handlers by prefix pattern.

    A path may be covered by several registered patterns; resolve() returns
    the most specific match — the longest covering pattern — regardless of
    the order routes were registered in.
    """

    def __init__(self):
        self._routes = []

    def add(self, pattern, handler, name=None):
        """Register a handler for a path prefix."""
        pattern = normalize_path(pattern)
        if any(route.pattern == pattern for route in self._routes):
            raise ValueError("duplicate route: {}".format(pattern))
        self._routes.append(Route(pattern, handler, name or pattern))

    def resolve(self, path):
        """The Route covering path, or None."""
        path = normalize_path(path)
        for route in self._routes:
            if matches(route.pattern, path):
                return route
        return None

    def routes(self):
        """A snapshot of the registered routes."""
        return list(self._routes)

path: src/router.py

content: """Path and string helpers."""


def normalize_path(path):
    """Collapse duplicate slashes and strip a trailing slash (except root)."""
    if not path.startswith("/"):
        path = "/" + path
    while "//" in path:
        path = path.replace("//", "/")
    if len(path) > 1 and path.endswith("/"):
        path = path[:-1]
    return path


def segments(path):
    """The non-empty segments of a path."""
    return [part for part in path.split("/") if part]


def clamp(value, low, high):
    """Restrict value to the inclusive [low, high] range."""
    return max(low, min(high, value))


def parse_int(text, default):
    """Parse text as an int, falling back to default."""
    try:
        return int(text)
    except (TypeError, ValueError):
        return default

path: src/utils.py

content: """Query-parameter validation."""

from .errors import ValidationError
from .utils import parse_int

SORT_FIELDS = ("name", "created", "total")


def require_fields(mapping, required):
    """Raise ValidationError unless every required key is present."""
    missing = [key for key in required if key not in mapping]
    if missing:
        raise ValidationError("missing fields: {}".format(", ".join(missing)))


def validate_sort(query):
    """Return the requested sort field, defaulting to the first allowed."""
    field = query.get("sort", SORT_FIELDS[0])
    if field not in SORT_FIELDS:
        raise ValidationError("cannot sort by {}".format(field))
    return field


def validate_flag(query, name):
    """Interpret a query flag as a boolean."""
    raw = query.get(name, "false").lower()
    if raw not in ("true", "false", "1", "0"):
        raise ValidationError("{} must be a boolean flag".format(name))
    return raw in ("true", "1")


def validate_limit(query, ceiling):
    """Return a positive item limit no greater than ceiling."""
    limit = parse_int(query.get("limit"), ceiling)
    if limit < 1:
        raise ValidationError("limit must be positive")
    return min(limit, ceiling)

path: src/validation.py

content: """Tests for the demo API: routing, middleware, handlers, and helpers."""

from src.app import build_app
from src.http import Request
from src.pagination import page_count, paginate
from src.router import Router
from src.utils import normalize_path


def get(app, path, query=None, headers=None):
    return app.handle(Request("GET", path, dict(query or {}), dict(headers or {})))


def test_users_listing():
    response = get(build_app(), "/api/users")
    assert response.status == 200
    assert response.body.startswith("users (6)")
    assert "- ada" in response.body


def test_users_export_route():
    response = get(build_app(), "/api/users/export", query={"emails": "true"})
    assert response.status == 200
    assert response.body.splitlines()[0] == "id,name,email"
    assert "u2,grace,grace@example.test" in response.body


def test_reports_summary():
    response = get(build_app(), "/api/reports")
    assert response.status == 200
    assert "summary report" in response.body
    assert "revenue=860" in response.body


def test_root_and_unknown_paths():
    app = build_app()
    assert "welcome to demo-api" in get(app, "/").body
    assert get(app, "/nope").status == 404
    assert get(app, "/health").body == "status: healthy"


def test_admin_gate_and_request_id():
    app = build_app()
    request = Request("GET", "/admin", {}, {})
    response = app.handle(request)
    assert response.status == 401
    assert app.settings.request_id_header in request.headers
    granted = get(app, "/admin", headers={"X-Token": "sesame"})
    assert "admin dashboard" in granted.body


def test_search_and_query_validation():
    app = build_app()
    assert "user:ada" in get(app, "/api/search", query={"q": "ada"}).body
    assert get(app, "/api/search").status == 400


def test_router_and_helpers_basics():
    router = Router()
    router.add("/x", "handler-x")
    assert router.resolve("/x/deep/path").pattern == "/x"
    assert router.resolve("/y") is None
    assert normalize_path("//a//b/") == "/a/b"
    assert paginate([1, 2, 3, 4, 5], 2, 2) == [3, 4]
    assert page_count([1, 2, 3, 4, 5], 2) == 3

path: tests/public/test_public.py

Reference patch:
--- a/src/router.py
+++ b/src/router.py
@@ -44,10 +44,12 @@ class Router:
     def resolve(self, path):
         """The Route covering path, or None."""
         path = normalize_path(path)
+        best = None
         for route in self._routes:
             if matches(route.pattern, path):
-                return route
-        return None
+                if best is None or len(route.pattern) > len(best.pattern):
+                    best = route
+        return best
 
     def routes(self):
         """A snapshot of the registered routes."""


Hidden acceptance tests (diff):
diff --git a/tests/hidden/test_hidden.py b/tests/hidden/test_hidden.py
new file mode 100644
--- /dev/null
+++ b/tests/hidden/test_hidden.py
@@ -0,0 +1,45 @@
+"""Hidden evaluation tests. Never agent-visible (DEC-015).
+
+FAIL_TO_PASS: resolve() returns the first registered covering route instead
+of the most specific one, so a prefix registered before a longer pattern
+swallows it; all three tests fail at baseline and pass once resolution
+honors the documented most-specific-wins contract.
+"""
+
+from src.app import build_app
+from src.http import Request
+from src.router import Router
+
+
+def test_daily_report_route_dispatches_to_daily():
+    response = build_app().handle(Request("GET", "/api/reports/daily", {}, {}))
+    assert response.status == 200
+    assert "daily report" in response.body
+    assert "orders_today=2" in response.body
+
+
+def test_specific_route_wins_when_registered_after_prefix():
+    router = Router()
+    router.add("/api", "api-root")
+    router.add("/api/users", "users")
+    assert router.resolve("/api/users").handler == "users"
+    assert router.resolve("/api/users/42").handler == "users"
+    assert router.resolve("/api").handler == "api-root"
+
+
+def test_most_specific_wins_across_registration_orders():
+    prefix_first = Router()
+    prefix_first.add("/docs", "docs")
+    prefix_first.add("/docs/guides", "guides")
+    prefix_first.add("/docs/guides/setup", "setup")
+
+    specific_first = Router()
+    specific_first.add("/docs/guides/setup", "setup")
+    specific_first.add("/docs/guides", "guides")
+    specific_first.add("/docs", "docs")
+
+    for router in (prefix_first, specific_first):
+        assert router.resolve("/docs/guides/setup").handler == "setup"
+        assert router.resolve("/docs/guides/setup/linux").handler == "setup"
+        assert router.resolve("/docs/guides").handler == "guides"
+        assert router.resolve("/docs/intro").handler == "docs"