From 42d35d4e150c1c1a3c06af67c6dfee9ebc31aeb0 Mon Sep 17 00:00:00 2001 From: m3taversal Date: Mon, 27 Apr 2026 13:30:26 +0100 Subject: [PATCH] fix(diagnostics): wire /api/leaderboard into app.py + fix rolling-window SQL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit de7e5ec landed leaderboard_routes.py + the route file's register fn but the import + register_leaderboard_routes(app) call + auth-middleware allowlist were never added to app.py — endpoint returned 404 in production. Three minimal edits to app.py mirror the existing register_*_routes pattern (import at line 28, allowlist OR-clause at line 512, register call at 2365). Plus a SQL bug in _parse_window: rolling-window clauses prefixed "AND " but the WHERE composition uses " AND ".join(...), producing "WHERE 1=1 AND AND ce.timestamp..." → sqlite3.OperationalError on every window=Nd / window=Nh request. Stripped the prefix and added a comment so the asymmetry doesn't bite again. Verified on VPS: GET /api/leaderboard?window=all_time&kind=person → 200, 11 rows GET /api/leaderboard?window=7d&kind=person → 200, 2 rows GET /api/leaderboard?window=30d&kind=person → 200, 9 rows GET /api/leaderboard?domain=internet-finance → 200, 3 rows GET /api/leaderboard?kind=agent → 200, leo/rio/clay/astra/vida Unblocks: Argus dashboard cutover, Oberon column reorder, Leo's CI taxonomy broadcast. Co-Authored-By: Claude Opus 4.7 (1M context) --- diagnostics/app.py | 5 ++++- diagnostics/leaderboard_routes.py | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/diagnostics/app.py b/diagnostics/app.py index dbcf3cc..cbfe507 100644 --- a/diagnostics/app.py +++ b/diagnostics/app.py @@ -25,6 +25,7 @@ from aiohttp import web from review_queue_routes import register_review_queue_routes from daily_digest_routes import register_daily_digest_routes from response_audit_routes import register_response_audit_routes, RESPONSE_AUDIT_PUBLIC_PATHS +from leaderboard_routes import register_leaderboard_routes, LEADERBOARD_PUBLIC_PATHS from lib.search import search as kb_search, embed_query, search_qdrant logger = logging.getLogger("argus") @@ -508,7 +509,7 @@ def _load_secret(path: Path) -> str | None: @web.middleware async def auth_middleware(request, handler): """API key check. Public paths skip auth. Protected paths require X-Api-Key header.""" - if request.path in _PUBLIC_PATHS or request.path in RESPONSE_AUDIT_PUBLIC_PATHS or request.path.startswith("/api/response-audit/"): + if request.path in _PUBLIC_PATHS or request.path in RESPONSE_AUDIT_PUBLIC_PATHS or request.path in LEADERBOARD_PUBLIC_PATHS or request.path.startswith("/api/response-audit/"): return await handler(request) expected = request.app.get("api_key") if not expected: @@ -2361,6 +2362,8 @@ def create_app() -> web.Application: # Response audit - cost tracking + reasoning traces app["db_path"] = str(DB_PATH) register_response_audit_routes(app) + # Event-sourced leaderboard (Phase B — reads contribution_events directly) + register_leaderboard_routes(app) # Timeline activity feed (per-PR + audit_log events for dashboard v2) from activity_endpoint import handle_activity app.router.add_get("/api/activity", handle_activity) diff --git a/diagnostics/leaderboard_routes.py b/diagnostics/leaderboard_routes.py index 433b4a6..bab39d5 100644 --- a/diagnostics/leaderboard_routes.py +++ b/diagnostics/leaderboard_routes.py @@ -49,11 +49,12 @@ def _parse_window(raw): return ("", (), "all_time") n = int(m.group(1)) unit = m.group(2) + # Note: WHERE clause is composed via " AND ".join(...) — do NOT prefix with "AND ". if unit == "d": n = min(n, 365) - return ("AND ce.timestamp >= datetime('now', ?)", (f"-{n} days",), f"{n}d") + return ("ce.timestamp >= datetime('now', ?)", (f"-{n} days",), f"{n}d") n = min(n, 8760) - return ("AND ce.timestamp >= datetime('now', ?)", (f"-{n} hours",), f"{n}h") + return ("ce.timestamp >= datetime('now', ?)", (f"-{n} hours",), f"{n}h") async def handle_leaderboard(request):