/** * /jobs/[id] — Investigation Bureau case file viewer. * * Server-rendered shell with the first snapshot fetched directly from * pg (one round-trip). A client island then polls /api/jobs/[id] every 3s * while the job is non-terminal (queued | running). * * Detectives: * - hypothesis_tournament → Sherlock Holmes * - evidence_chain → Edmond Locard * * Renders: * - Phase tracker (queued → claimed → running → complete | failed) * - Hypothesis cards w/ prior+posterior bars + Tetlock confidence_band badge * - Evidence cards w/ grade A/B/C badge + verbatim_excerpt + bbox crop link */ import { notFound } from "next/navigation"; import Link from "next/link"; import { pgQuery } from "@/lib/retrieval/db"; import { AuthBar } from "@/components/auth-bar"; import { JobStatusPoller } from "@/components/job-status-poller"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; interface InitialJob { job_id: string; kind: string; payload: Record | null; status: string; worker_id: string | null; started_at: string | null; finished_at: string | null; outputs: unknown; error: string | null; created_at: string; } export default async function JobPage({ params, }: { params: Promise<{ id: string }> }) { const { id } = await params; if (!/^[0-9a-f-]{36}$/i.test(id)) notFound(); const rows = await pgQuery( `SELECT job_id, kind, payload, status, worker_id, started_at, finished_at, outputs, error, created_at FROM public.investigation_jobs WHERE job_id = $1`, [id], ).catch(() => [] as InitialJob[]); const job = rows[0]; if (!job) notFound(); const detective = job.kind === "hypothesis_tournament" ? "holmes" : job.kind === "contradiction_scan" ? "dupin" : job.kind === "red_team_review" ? "schneier" : "locard"; const detectiveName = detective === "holmes" ? "Sherlock Holmes" : detective === "dupin" ? "C. Auguste Dupin" : detective === "schneier" ? "Bruce Schneier" : "Edmond Locard"; const detectiveSubtitle = detective === "holmes" ? "Hypothesis tournament · rival hypotheses with Bayesian update" : detective === "dupin" ? "Contradiction scan · pairs of chunks in irreconcilable tension" : detective === "schneier" ? "Red-team review · hidden assumptions, failure modes, alt explanations" : "Evidence chain · verbatim quotes with chain of custody (Locard)"; const detectiveTone = detective === "holmes" ? "text-[#7fdbff]" : detective === "dupin" ? "text-[#ff8a4d]" : detective === "schneier" ? "text-[#ff3344]" : "text-[#06d6a0]"; const detectiveBg = detective === "holmes" ? "from-[rgba(127,219,255,0.08)]" : detective === "dupin" ? "from-[rgba(255,138,77,0.08)]" : detective === "schneier" ? "from-[rgba(255,51,68,0.08)]" : "from-[rgba(6,214,160,0.08)]"; const payload = (job.payload ?? {}) as Record; const question = (payload.question ?? payload.topic ?? payload.hypothesis_id) as string | undefined; const questionLabel = job.kind === "contradiction_scan" ? "Topic" : job.kind === "red_team_review" ? "Hypothesis under attack" : "Question"; const docId = payload.doc_id as string | undefined; return (
disclosure.top / investigation / {job.job_id.slice(0, 8)}

{detectiveName}

{detectiveSubtitle}

{detective}
{question && (
{questionLabel}
{question}
)} {docId && (
Scope: {docId}
)}
); }