Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,37 @@ describe("ReportAuditView", () => {
expect(markup).toContain("Completed");
expect(markup).toContain("Runtime output tail");
expect(markup).toContain("State history");
expect(markup).toContain("data-producer-context");
expect(markup).toContain("data-runtime-output-tail");
expect(markup).toContain("print:break-inside-avoid");
expect(markup).toContain("print:max-h-none");
expect(markup).toContain("print:overflow-visible");
expect(markup).toContain("<local-path>");
expect(markup).toContain("token=<redacted>");
expect(markup).toContain("<runtime-url>");
expect(markup).not.toContain("C:\\runtime\\private");
expect(markup).not.toContain("abc123");
expect(markup).not.toContain("localhost:8780");
});

it("renders a public-safe disconnected producer state", () => {
const markup = renderToStaticMarkup(
<ReportAuditView
locale="en-US"
rows={rows}
provenance={{}}
historyPlaceholder="No history data"
producerContext={{
unavailable: true,
}}
/>,
);

expect(markup).toContain("Producer context");
expect(markup).toContain("Runtime disconnected");
expect(markup).toContain("Job detail is temporarily unavailable; the report remains based on the current public snapshot.");
expect(markup).toContain("data-producer-context");
expect(markup).not.toContain("localhost");
expect(markup).not.toContain("ECONNREFUSED");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type ReportProducerContext = {
updatedAt?: string | null;
stdoutTail?: string | null;
stderrTail?: string | null;
unavailable?: boolean;
stateHistory?: Array<{
state: string;
timestamp?: string | null;
Expand Down Expand Up @@ -106,6 +107,9 @@ export function ReportAuditView({
producerStatus: "状态",
producerUpdated: "更新时间",
outputTail: "Runtime 输出尾部",
producerUnavailableTitle: "Runtime 未连接",
producerUnavailableBody:
"任务详情暂时不可用;报告仍基于当前公开快照展示。",
stateHistory: "状态历史",
noProvenanceData: "暂无溯源数据。",
}
Expand All @@ -132,6 +136,9 @@ export function ReportAuditView({
producerStatus: "Status",
producerUpdated: "Updated",
outputTail: "Runtime output tail",
producerUnavailableTitle: "Runtime disconnected",
producerUnavailableBody:
"Job detail is temporarily unavailable; the report remains based on the current public snapshot.",
stateHistory: "State history",
noProvenanceData: "No provenance data available.",
};
Expand All @@ -141,11 +148,13 @@ export function ReportAuditView({
const stdoutTail = sanitizedTail(producerContext?.stdoutTail);
const stderrTail = sanitizedTail(producerContext?.stderrTail);
const stateHistory = producerContext?.stateHistory ?? [];
const producerUnavailable = producerContext?.unavailable === true;
const hasProducerContext = !!producerContext && (
hasValue(producerStatus)
|| hasValue(producerContext.updatedAt)
|| hasValue(stdoutTail)
|| hasValue(stderrTail)
|| producerUnavailable
|| stateHistory.length > 0
);

Expand Down Expand Up @@ -304,8 +313,23 @@ export function ReportAuditView({
<div className="space-y-2 text-[13px] text-muted-foreground">
<p>{historyPlaceholder}</p>
{hasProducerContext ? (
<div className="mt-3 rounded-2xl border border-border bg-background p-3">
<h3 className="text-[13px] font-bold text-foreground">{t.producerTitle}</h3>
<div
data-producer-context=""
className="mt-3 rounded-2xl border border-border bg-background p-3 print:break-inside-avoid print:bg-white"
>
<div className="flex flex-wrap items-center justify-between gap-2">
<h3 className="text-[13px] font-bold text-foreground">{t.producerTitle}</h3>
{producerUnavailable ? (
<span className="rounded-xl border border-[var(--warning)]/25 bg-[var(--warning-soft-strong)] px-2 py-1 text-[11px] font-semibold text-[var(--warning)]">
{t.producerUnavailableTitle}
</span>
) : null}
</div>
{producerUnavailable ? (
<p className="mt-2 text-[13px] leading-5 text-muted-foreground">
{t.producerUnavailableBody}
</p>
) : null}
<dl className="mt-3 grid gap-3 text-[13px] sm:grid-cols-2">
{producerStatus ? (
<div>
Expand Down Expand Up @@ -345,7 +369,10 @@ export function ReportAuditView({
<div className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
{t.outputTail}
</div>
<pre className="mt-2 max-h-44 overflow-y-auto whitespace-pre-wrap break-all rounded-xl border border-border bg-card p-3 mono text-[11px] leading-relaxed text-muted-foreground">
<pre
data-runtime-output-tail=""
className="mt-2 max-h-44 overflow-y-auto whitespace-pre-wrap break-all rounded-xl border border-border bg-card p-3 mono text-[11px] leading-relaxed text-muted-foreground print:max-h-none print:overflow-visible print:break-inside-avoid"
>
{[stdoutTail, stderrTail].filter(Boolean).join("\n")}
</pre>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,36 @@ describe("TrackReportPage", () => {
expect(markup).not.toContain("localhost:8780");
});

it("shows a disconnected producer state when job detail is unavailable", async () => {
vi.stubGlobal("fetch", vi.fn(async (input: RequestInfo | URL) => {
const url = String(input);
if (url.includes("/api/v1/audit/jobs/job_demo_004")) {
return new Response("ECONNREFUSED http://localhost:8765", { status: 503 });
}

return new Response(null, { status: 404 });
}));

const markup = renderToStaticMarkup(
await renderTrackReportPage({
locale: "en-US",
params: { track: "black-box" },
searchParams: {
view: "audit",
job: "job_demo_004",
contract: "recon_artifact_mainline",
model: "stable-diffusion-v1-4",
auc: "0.849",
},
}),
);

expect(markup).toContain("Runtime disconnected");
expect(markup).toContain("Job detail is temporarily unavailable; the report remains based on the current public snapshot.");
expect(markup).not.toContain("ECONNREFUSED");
expect(markup).not.toContain("localhost:8765");
});

it("keeps completed job context safe when no snapshot row matches", async () => {
const markup = renderToStaticMarkup(
await renderTrackReportPage({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,12 @@ async function fetchProducerContext(jobContext: ReturnType<typeof buildJobContex
{ timeoutMs: DEFAULT_SERVER_FETCH_TIMEOUT_MS },
);
if (!response.ok) {
return undefined;
return { unavailable: true };
}

const job = readJobEnvelope(await response.json());
if (!job) {
return undefined;
return { unavailable: true };
}

return {
Expand All @@ -260,7 +260,7 @@ async function fetchProducerContext(jobContext: ReturnType<typeof buildJobContex
stateHistory: readStateHistory(job.state_history),
};
} catch {
return undefined;
return { unavailable: true };
}
}

Expand Down
5 changes: 2 additions & 3 deletions docs/platform-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ This roadmap tracks product-facing Platform work. It avoids private deployment d
| Docker images | Active | GHCR publishes web and API images with immutable `sha-<short-sha>` tags |
| Deployment traceability | Active | Gateway health exposes redacted build revision and snapshot status |
| Workspace observability | Active | Shell status drawer and Settings show data mode, snapshot state, and build revision |
| Reports | Active | Evidence stack, provenance, track review links, job-linked producer context, charts, PDF and CSV export |
| Reports | Active | Evidence stack, provenance, track review links, print-safe job-linked producer context, charts, PDF and CSV export |
| Demo mode | Active | Snapshot-backed demo data keeps the workspace reviewable offline |
| Runtime response facade | Active | Audit job API responses and report-side producer details are normalized through a public-safe redaction layer before rendering |
| Runtime response facade | Active | Audit job API responses and report-side producer details are normalized through a public-safe redaction layer before rendering, with generic disconnected states when Runtime is unavailable |
| Workspace controls | Active | Audit filters and create-audit wizard expose named controls, step state, and reduced-motion behavior |
| Public API docs | Active | Docs record snapshot-backed API contracts, optional evidence fields, and request-time fallback boundaries |

Expand All @@ -29,7 +29,6 @@ This roadmap tracks product-facing Platform work. It avoids private deployment d
| Priority | Track | Work |
| --- | --- | --- |
| P1 | Reports | Improve printable report pagination, table wrapping, and long-evidence layout |
| P1 | Audit loop | Improve report-side producer context layout for print/export and disconnected Runtime states |
| P2 | Deployment | Add optional image provenance verification helpers for GHCR and local archive deployments |
| P2 | Account | Polish account security state for linked providers, verified email, and password access |
| P2 | Accessibility | Add menu roles, chart text summaries, and stronger focus handling in shared primitives |
Expand Down
Loading