disclosure-bureau/web/components/red-team-request-button.tsx

64 lines
1.9 KiB
TypeScript
Raw Normal View History

W3.8: Schneier red-team detective + /h/[hypothesisId] dossier page Adds the fourth AI detective in the Investigation Bureau runtime: Bruce Schneier, who attacks an existing hypothesis as a red-team operator. Runtime: - prompts/schneier.md — discipline (don't disprove, just attack; structured output with hidden_assumptions, failure_modes, alternative_explanations, recommended_tests, verdict_one_sentence; severity ∈ {low, medium, high}; emit INSUFFICIENT_HYPOTHESIS when the input is too thin) - src/detectives/schneier.ts — reads the hypothesis row + evidence chain (joined via evidence_refs FK), feeds Claude with the arguments + verbatim quotes, parses strict JSON object - src/tools/write_red_team_review.ts — UPDATEs hypotheses.reviewed_by + updated_at; APPENDS (or replaces if re-reviewed) a structured "## Red-team review (Schneier · X severity)" section to case/hypotheses/H-NNNN.md. Caps each list at 5 entries × 240 chars, validates verdict ≤ 280 chars. - orchestrator: new `red_team_review` kind dispatching to runSchneier Chat + UI: - request_investigation gains kind=red_team_review + hypothesis_id arg (validated against H-NNNN regex); detective auto-resolves to schneier - chat-bubble inline card paints Schneier in red (#ff3344) - /jobs/[id] page swaps title/subtitle/tone per detective; the "Question" label becomes "Hypothesis under attack" for red_team_review New /h/[hypothesisId] page (hypothesis dossier): - Server-rendered from public.hypotheses + public.evidence (joined via evidence_refs FK + chunk lookup) - Header: ID + creator + reviewer (highlighted when Schneier has visited), position as headline, question subtitle, Tetlock band - Prior + posterior bars with Δ-delta indicator - Argument grid: argument_for (green) vs argument_against (pink) side-by-side with [[wiki-link]] auto-linking to source chunks - Evidence chain: each E-NNNN with Grade A/B/C badge, verbatim blockquote, link to source page - Red-team review panel: parses the markdown section in the case file (severity badge, verdict, 4 bullet panels for hidden_assumptions / failure_modes / alternative_explanations / recommended_tests). Empty state when not yet reviewed. RedTeamRequestButton client component + POST /api/h/[id]/red-team — authenticated user can trigger Schneier in one click; UI swaps to "acompanhar" link to /jobs/[id] once queued. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 00:48:12 +00:00
"use client";
/**
* RedTeamRequestButton POSTs to /api/h/[id]/red-team to enqueue a Schneier
* red_team_review job. Shows the job_id + link to /jobs/[id] once submitted.
*/
import { useState } from "react";
import Link from "next/link";
import { ArrowUpRight } from "lucide-react";
export function RedTeamRequestButton({
hypothesisId,
alreadyReviewed,
}: { hypothesisId: string; alreadyReviewed: boolean }) {
const [pending, setPending] = useState(false);
const [jobId, setJobId] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
async function requestReview() {
setPending(true);
setError(null);
try {
const r = await fetch(`/api/h/${hypothesisId}/red-team`, { method: "POST" });
const d = (await r.json()) as { job_id?: string; error?: string; message?: string };
if (!r.ok || !d.job_id) {
setError(d.error || d.message || `HTTP ${r.status}`);
return;
}
setJobId(d.job_id);
} catch (e) {
setError((e as Error).message);
} finally {
setPending(false);
}
}
if (jobId) {
return (
<Link
href={`/jobs/${jobId}`}
target="_blank"
className="inline-flex items-center gap-1 text-[11px] font-mono text-[#ff3344] hover:underline"
>
Schneier em ação acompanhar <ArrowUpRight size={11} />
</Link>
);
}
return (
<div className="flex items-center gap-2">
<button
onClick={requestReview}
disabled={pending}
className="px-3 py-1 rounded border border-[#ff3344] text-[11px] font-mono text-[#ff3344] hover:bg-[rgba(255,51,68,0.06)] disabled:opacity-40 disabled:cursor-not-allowed"
>
{pending ? "Enfileirando…" : alreadyReviewed ? "Re-revisar (Schneier)" : "Acionar Schneier (red-team)"}
</button>
{error && (
<span className="text-[10px] font-mono text-[#ff6ec7]">{error}</span>
)}
</div>
);
}