import { useState } from "react"; import { Link, useParams, useSearchParams } from "react-router-dom"; import { useMutation, useQuery } from "@tanstack/react-query"; import { Download, PackageOpen, Play, RefreshCw } from "lucide-react"; import { apiFetchBlob, saveBlob } from "../api/client"; import { explainObjectValidation, getObject, getObjectChain, getObjectProjection, getObjectValidation, rawObjectUrl, } from "../api/service"; import type { ValidationIssue } from "../api/schemas"; import { CopyableValue } from "../components/CopyableValue"; import { DataTable, type Column } from "../components/DataTable"; import { PageHeader } from "../components/PageHeader"; import { Panel } from "../components/Panel"; import { CertificateView, ProjectionView } from "../components/ProjectionView"; import { StatusPill } from "../components/StatusPill"; import { EmptyBlock, ErrorBlock, LoadingBlock, Notice } from "../components/StateBlock"; import { TabPanel, Tabs } from "../components/Tabs"; import { WorkflowStatus } from "../components/WorkflowStatus"; import { formatInt, objectTypeLabel } from "../lib/format"; import { eeCertificateProjection } from "../lib/projection"; import { withRunParam } from "../lib/run"; import { useExportJob } from "../lib/useExportJob"; import { useRun } from "../lib/useRun"; function filenameFor(uri: string | undefined, sha256: string | undefined, type: string): string { const last = uri?.split("/").filter(Boolean).pop(); if (last && last.length <= 128) return last; const ext = type === "mft" ? ".mft" : type === "crl" ? ".crl" : type === "roa" ? ".roa" : type === "asa" ? ".asa" : ".cer"; return `${(sha256 ?? "object").slice(0, 16)}${ext}`; } function IssueList({ issues }: { issues: ValidationIssue[] }) { if (issues.length === 0) return

No issues recorded.

; return ( ); } function ValidationTab({ runId, objectInstanceId }: { runId: string; objectInstanceId: string }) { const [forceRefresh, setForceRefresh] = useState(false); const validationQuery = useQuery({ queryKey: ["object-validation", runId, objectInstanceId], queryFn: () => getObjectValidation(runId, objectInstanceId), staleTime: 5 * 60_000, }); const explainMutation = useMutation({ mutationFn: () => explainObjectValidation(runId, objectInstanceId, { forceRefresh }), }); if (validationQuery.isPending) return ; if (validationQuery.isError) { return ( validationQuery.refetch()} title="Failed to load validation summary" /> ); } const validation = validationQuery.data; const explain = explainMutation.data; return (
Final status
Audit result
Detail
{validation.detailSummary ?? "—"}

File validation (parsevalidate)

Chain validation (chainvalidate)

Explain validation

{explainMutation.isError ? ( Explain failed: {explainMutation.error.message} ) : null} {explain ? (
Explain mode: {explain.explainMode ?? "audit projection"} —{" "} {explain.authoritative ? "authoritative revalidation." : "not a full revalidation; derived from the run audit trail."}
Final status
Parse stage
Chain stage
{" "} {formatInt(explain.chainvalidate?.edgesCount)} chain edges
{explain.chainvalidate?.note ? ( <>
Note
{explain.chainvalidate.note}
) : null}
) : null}
); } function ChainTab({ runId, objectInstanceId }: { runId: string; objectInstanceId: string }) { const chainQuery = useQuery({ queryKey: ["object-chain", runId, objectInstanceId], queryFn: () => getObjectChain(runId, objectInstanceId), staleTime: 5 * 60_000, }); if (chainQuery.isPending) return ; if (chainQuery.isError) { return chainQuery.refetch()} title="Failed to load chain" />; } const edges = chainQuery.data; if (edges.length === 0) { return ; } const columns: Column<(typeof edges)[number]>[] = [ { key: "relation", header: "Relation", render: (edge) => {edge.relation} }, { key: "from", header: "From URI", render: (edge) => , }, { key: "to", header: "To URI", render: (edge) => , }, { key: "status", header: "Status", render: (edge) => }, ]; return `${e.relation}:${e.fromUri}:${e.toUri}`} caption="Certificate chain edges" />; } export default function ObjectDetailPage() { const { objectInstanceId = "" } = useParams(); const { runId } = useRun(); const [searchParams, setSearchParams] = useSearchParams(); const tab = searchParams.get("tab") ?? "parsed"; const objectQuery = useQuery({ queryKey: ["object", runId, objectInstanceId], queryFn: () => getObject(runId, objectInstanceId), }); // Always fetch the parsed projection: the Parsed tab needs it, and the // conditional "EE Parsed" tab derives from it (CMS-signed objects only). const projectionQuery = useQuery({ queryKey: ["object-parsed", runId, objectInstanceId], queryFn: () => getObjectProjection(runId, objectInstanceId), staleTime: 5 * 60_000, }); const rawMutation = useMutation({ mutationFn: async () => { const blob = await apiFetchBlob(rawObjectUrl(runId, objectInstanceId)); const object = objectQuery.data; saveBlob(blob, filenameFor(object?.uri, object?.sha256, object?.objectType ?? "")); }, }); const { startMutation: exportMutation, jobQuery: exportJobQuery, running: exportRunning, } = useExportJob(runId, { scope: "object_set", objectInstanceIds: [objectInstanceId] }); if (objectQuery.isError) { return (
objectQuery.refetch()} title="Failed to load object" />
); } if (objectQuery.isPending) { return (
); } const object = objectQuery.data; const eeProjection = eeCertificateProjection(projectionQuery.data ?? null); const setTab = (next: string) => { const params = new URLSearchParams(searchParams); params.set("tab", next); setSearchParams(params, { preventScrollReset: true }); }; return (
{objectTypeLabel(object.objectType)} object } subtitle={} actions={ <> } /> {rawMutation.isError ? ( Raw download failed: {rawMutation.error.message}. The query service needs --repo-bytes-db to serve raw bytes. ) : null} {exportMutation.isError ? ( Export failed to start: {exportMutation.error.message} ) : null} {exportJobQuery.data ? : null} {exportJobQuery.data?.status === "complete" ? ( Export complete — {formatInt(exportJobQuery.data.objectCount)} objects. Find it any time on the Exports page. ) : null}
Object type
{objectTypeLabel(object.objectType)}
URI
SHA-256
Instance ID
Result
Reject reason
{object.rejectReason ?? }
Detail
{object.detailSummary ?? }
Repository
{object.repoId}
Publication point
{object.ppId}
{projectionQuery.isPending ? ( ) : projectionQuery.isError ? ( projectionQuery.refetch()} title="Failed to load parsed projection" /> ) : ( )}
{eeProjection ? (
) : tab === "ee" ? (
This object has no embedded EE certificate in its CMS wrapper.
) : null}
); }