"""Traceability endpoint — user-facing per-turn provenance (KM-691). `GET /api/v1/traceability?analysis_id=&message_id=` returns the provenance record for one assistant turn: planning / tool calls (with real inputs + outputs) / data sources (with the executed query). One JSONB row per assistant `message_id`, written by the chat pipeline right before the `done` SSE event; the FE fires this GET on `done`. Renamed from the contracted `/api/v1/observability` (team decision 2026-07-06) so it is never confused with the Langfuse *observability* stack (engineering-only, PII-masked). No auth — Go fronts Python. """ from fastapi import APIRouter, HTTPException, Query from src.middlewares.logging import get_logger, log_execution from src.traceability import PostgresTraceabilityStore, TraceabilityPayload logger = get_logger("traceability_api") router = APIRouter(prefix="/api/v1", tags=["Traceability"]) # Warm, process-shared store (mirrors the chat endpoints' module-level instances). _store = PostgresTraceabilityStore() @router.get("/traceability", response_model=TraceabilityPayload) @log_execution(logger) async def get_traceability( analysis_id: str = Query(..., description="Analysis/session id"), message_id: str = Query( ..., description="Assistant turn id, taken from the `done` SSE event" ), ) -> TraceabilityPayload: """Fetch one turn's provenance record. 404 while the turn is still running or if the id is unknown (the FE never gets a `message_id` for error turns, so 404 is the correct answer there).""" payload = await _store.get(analysis_id, message_id) if payload is None: raise HTTPException( status_code=404, detail=f"No traceability for message '{message_id}' yet.", ) return payload