SPB Git

spb/autoscholar

Public
6.2 KB · 155 lines tsx
1import { STAGES } from "@autoscholar/schemas";2import { fmtDate, shortHash } from "@/lib/format";3import { IconChevron } from "./Icons";4import { StatusBadge } from "./StatusBadge";56type Gate = { gate: string; status: string; severity: string; message: string };78/** ResearchProvenance — expandable, verifiable record: stages, models, hashes, gates, warnings. */9export function ResearchProvenance({10  prov,11  qa,12  texReleased,13  meta,14}: {15  prov: Record<string, unknown>;16  qa: { gates?: Gate[]; warnings?: string[]; policyVersion?: string; evaluatedAt?: string };17  texReleased: boolean;18  meta: Record<string, unknown>;19}) {20  const gates = qa.gates ?? [];21  const passed = gates.filter((g) => g.status === "pass").length;22  const counted = gates.filter((g) => g.status !== "skip").length;23  const stagesDone = (prov.stagesCompleted as string[] | undefined) ?? [];24  const toolchain = (prov.toolchain as Record<string, string> | undefined) ?? {};25  const Row = ({ k, v, mono = false }: { k: string; v: React.ReactNode; mono?: boolean }) => (26    <div className="grid grid-cols-[9rem_1fr] gap-x-3 border-b py-2 text-sm last:border-0 hairline sm:grid-cols-[11rem_1fr]">27      <dt className="label pt-0.5">{k}</dt>28      <dd className={`min-w-0 break-words ${mono ? "font-mono text-xs" : ""}`}>{v}</dd>29    </div>30  );31  return (32    <div className="surface overflow-hidden">33      <div className="grid gap-4 border-b p-5 hairline sm:grid-cols-3">34        <div>35          <p className="label">Quality gates</p>36          <p className="serif mt-1 text-2xl">{counted ? `${passed}/${counted}` : "—"}</p>37          <p className="subtle text-xs">38            {qa.evaluatedAt ? `verified ${fmtDate(qa.evaluatedAt)}` : "not recorded"}39          </p>40        </div>41        <div>42          <p className="label">Stages completed</p>43          <p className="serif mt-1 text-2xl">44            {stagesDone.length ? `${stagesDone.length}/${STAGES.length - 2}` : "—"}45          </p>46          <p className="subtle text-xs">on the research worker</p>47        </div>48        <div>49          <p className="label">Policy</p>50          <p className="serif mt-1 text-2xl">{qa.policyVersion ?? "—"}</p>51          <p className="subtle text-xs">deterministic, server-side</p>52        </div>53      </div>54      <dl className="px-5 py-2">55        <Row k="Research model" v={String(prov.model ?? "—")} mono />56        <Row k="Reviewer model" v={String(prov.reviewerModel ?? "—")} mono />57        <Row58          k="Instruction"59          v={`v${String(prov.instructionVersion ?? "?")} · ${shortHash(prov.instructionSha256 as string)}`}60          mono61        />62        <Row k="Manifest digest" v={shortHash(prov.manifestSha256 as string, 16)} mono />63        <Row64          k="Execution"65          v={66            prov.startedAt67              ? `${fmtDate(prov.startedAt as string)} → ${prov.completedAt ? fmtDate(prov.completedAt as string) : "?"} · ${String(prov.infrastructure ?? "MacLustr")}`68              : "—"69          }70        />71        <Row72          k="Toolchain"73          v={74            Object.keys(toolchain).length75              ? Object.entries(toolchain)76                  .map(([k, v]) => `${k} ${v}`)77                  .join(" · ")78              : "not recorded"79          }80          mono81        />82        <Row83          k="LaTeX source"84          v={85            texReleased86              ? "Released; the PDF was rebuilt from it in a clean environment during verification."87              : "Not released."88          }89        />90        <Row k="Data availability" v={(meta.dataAvailability as string) || "Not applicable or not stated."} />91        <Row k="Code availability" v={(meta.codeAvailability as string) || "Not applicable or not stated."} />92        <Row93          k="Ethics"94          v={95            (meta.ethics as string) ||96            "No restricted activities; public data, mathematics or simulations only."97          }98        />99      </dl>100      {stagesDone.length > 0 && (101        <div className="border-t px-5 py-4 hairline">102          <p className="label mb-2">Pipeline record</p>103          <ol className="flex flex-wrap gap-1.5">104            {STAGES.map((s) => {105              const done =106                stagesDone.includes(s.key) || (s.key === "quality" && counted > 0) || s.key === "publication";107              return (108                <li key={s.key} className={`badge ${done ? "badge-sci" : ""}`} title={s.name}>109                  {s.index} {s.name}110                </li>111              );112            })}113          </ol>114        </div>115      )}116      {gates.length > 0 && (117        <details className="group border-t hairline">118          <summary className="flex items-center justify-between px-5 py-3 text-sm">119            <span>Gate-by-gate evidence ({gates.length})</span>120            <IconChevron size={16} className="subtle transition-transform group-open:rotate-90" />121          </summary>122          <ul className="space-y-1.5 px-5 pb-4 text-xs">123            {gates.map((g) => (124              <li key={g.gate} className="grid grid-cols-[auto_1fr] items-start gap-2">125                <StatusBadge status={g.status} />126                <span>127                  <span className="font-mono">{g.gate}</span> <span className="subtle">({g.severity})</span>128                  <span className="muted"> — {g.message}</span>129                </span>130              </li>131            ))}132          </ul>133        </details>134      )}135      {qa.warnings && qa.warnings.length > 0 && (136        <details className="group border-t hairline">137          <summary className="flex items-center justify-between px-5 py-3 text-sm">138            <span>Non-blocking warnings recorded at verification ({qa.warnings.length})</span>139            <IconChevron size={16} className="subtle transition-transform group-open:rotate-90" />140          </summary>141          <ul className="muted list-disc space-y-1 px-5 pb-4 pl-9 text-xs">142            {qa.warnings.map((w) => (143              <li key={w}>{w}</li>144            ))}145          </ul>146        </details>147      )}148      <p className="subtle border-t px-5 py-3 text-xs hairline">149        Artifact digests (SHA-256) accompany every download; the complete list is in150        provenance/checksums.sha256.151      </p>152    </div>153  );154}155
Files · main
Menu

Theme