import { STAGES } from "@autoscholar/schemas"; import { fmtDate, shortHash } from "@/lib/format"; import { IconChevron } from "./Icons"; import { StatusBadge } from "./StatusBadge"; type Gate = { gate: string; status: string; severity: string; message: string }; /** ResearchProvenance — expandable, verifiable record: stages, models, hashes, gates, warnings. */ export function ResearchProvenance({ prov, qa, texReleased, meta, }: { prov: Record; qa: { gates?: Gate[]; warnings?: string[]; policyVersion?: string; evaluatedAt?: string }; texReleased: boolean; meta: Record; }) { const gates = qa.gates ?? []; const passed = gates.filter((g) => g.status === "pass").length; const counted = gates.filter((g) => g.status !== "skip").length; const stagesDone = (prov.stagesCompleted as string[] | undefined) ?? []; const toolchain = (prov.toolchain as Record | undefined) ?? {}; const Row = ({ k, v, mono = false }: { k: string; v: React.ReactNode; mono?: boolean }) => (
{k}
{v}
); return (

Quality gates

{counted ? `${passed}/${counted}` : "—"}

{qa.evaluatedAt ? `verified ${fmtDate(qa.evaluatedAt)}` : "not recorded"}

Stages completed

{stagesDone.length ? `${stagesDone.length}/${STAGES.length - 2}` : "—"}

on the research worker

Policy

{qa.policyVersion ?? "—"}

deterministic, server-side

`${k} ${v}`) .join(" · ") : "not recorded" } mono />
{stagesDone.length > 0 && (

Pipeline record

    {STAGES.map((s) => { const done = stagesDone.includes(s.key) || (s.key === "quality" && counted > 0) || s.key === "publication"; return (
  1. {s.index} {s.name}
  2. ); })}
)} {gates.length > 0 && (
Gate-by-gate evidence ({gates.length})
    {gates.map((g) => (
  • {g.gate} ({g.severity}) — {g.message}
  • ))}
)} {qa.warnings && qa.warnings.length > 0 && (
Non-blocking warnings recorded at verification ({qa.warnings.length})
    {qa.warnings.map((w) => (
  • {w}
  • ))}
)}

Artifact digests (SHA-256) accompany every download; the complete list is in provenance/checksums.sha256.

); }