/** * POST /api/h/[hypothesisId]/red-team — enqueue a Schneier red_team_review * investigation job for the given hypothesis. Returns { job_id, status_url }. * * Requires an authenticated user (any role). Audit captures the requester's * email in triggered_by so the runtime can attribute the cost. */ import { NextResponse } from "next/server"; import { pgQuery } from "@/lib/retrieval/db"; import { createClient, isSupabaseConfigured } from "@/lib/supabase/server"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; export async function POST( _request: Request, ctx: { params: Promise<{ hypothesisId: string }> }, ) { const { hypothesisId } = await ctx.params; if (!/^H-\d{4}$/.test(hypothesisId)) { return NextResponse.json({ error: "bad_hypothesis_id" }, { status: 400 }); } let triggered_by = "user:anonymous"; if (isSupabaseConfigured()) { const supabase = await createClient(); const { data: { user } } = await supabase.auth.getUser(); if (!user) { return NextResponse.json({ error: "unauthenticated" }, { status: 401 }); } triggered_by = `user:${user.email ?? user.id}`; } // Verify hypothesis exists. const found = await pgQuery<{ hypothesis_id: string }>( `SELECT hypothesis_id FROM public.hypotheses WHERE hypothesis_id = $1`, [hypothesisId], ).catch(() => []); if (found.length === 0) { return NextResponse.json({ error: "not_found", hypothesis_id: hypothesisId }, { status: 404 }); } try { const rows = await pgQuery<{ job_id: string }>( `INSERT INTO public.investigation_jobs (kind, payload, triggered_by, status) VALUES ('red_team_review', $1::jsonb, $2, 'queued') RETURNING job_id`, [JSON.stringify({ hypothesis_id: hypothesisId }), triggered_by], ); const job_id = rows[0]?.job_id; if (!job_id) return NextResponse.json({ error: "insert_failed" }, { status: 500 }); return NextResponse.json({ job_id, status: "queued", status_url: `/jobs/${job_id}`, eta_seconds: 30, }); } catch (e) { return NextResponse.json({ error: "db_unavailable", message: (e as Error).message }, { status: 503 }); } }