feat(workbench-ui): add Trace practice evidence panel scaffold (#901)
This commit is contained in:
parent
8c3898c1e1
commit
d3df22e24a
8 changed files with 707 additions and 0 deletions
169
workbench-ui/src/app/trace/PracticeEvidencePanel.tsx
Normal file
169
workbench-ui/src/app/trace/PracticeEvidencePanel.tsx
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
import { useMemo } from "react";
|
||||
import type { PracticeEvidence } from "../../types/practiceEvidence";
|
||||
import { MetadataTable } from "../../design/components/MetadataTable/MetadataTable";
|
||||
import { StableJsonViewer } from "../../design/components/StableJsonViewer";
|
||||
import { EmptyState } from "../../design/components/states/EmptyState";
|
||||
import { ErrorState } from "../../design/components/states/ErrorState";
|
||||
import { LoadingState } from "../../design/components/states/LoadingState";
|
||||
import { tracePracticeReproducer } from "./practiceEvidenceEndpoint";
|
||||
import {
|
||||
type PracticeEvidenceDetailSection,
|
||||
practiceEvidencePanelModel,
|
||||
} from "./practiceEvidencePanelModel";
|
||||
|
||||
export interface PracticeEvidencePanelProps {
|
||||
evidence?: PracticeEvidence | null;
|
||||
isLoading: boolean;
|
||||
error: unknown;
|
||||
turnId: number;
|
||||
errorMessage: (error: unknown) => string;
|
||||
}
|
||||
|
||||
function rowValue(value: string) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function DetailSection({ section }: { section: PracticeEvidenceDetailSection }) {
|
||||
return (
|
||||
<section className="grid gap-2 rounded-md border border-[var(--color-border-subtle)] p-3">
|
||||
<h3 className="m-0 text-xs font-semibold uppercase text-[var(--color-text-secondary)]">
|
||||
{section.title}
|
||||
</h3>
|
||||
{section.items.length === 0 ? (
|
||||
<p className="m-0 text-sm text-[var(--color-text-secondary)]">{section.emptyMessage}</p>
|
||||
) : (
|
||||
<div className="grid gap-3">
|
||||
{section.items.map((item) => (
|
||||
<article className="grid gap-2" key={item.title}>
|
||||
<h4 className="m-0 font-mono text-xs text-[var(--color-text-primary)]">
|
||||
{item.title}
|
||||
</h4>
|
||||
<MetadataTable
|
||||
rows={item.rows.map((row) => ({
|
||||
key: row.key,
|
||||
value: rowValue(row.value),
|
||||
mono: true,
|
||||
}))}
|
||||
/>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function PracticeEvidencePanel({
|
||||
evidence,
|
||||
isLoading,
|
||||
error,
|
||||
turnId,
|
||||
errorMessage,
|
||||
}: PracticeEvidencePanelProps) {
|
||||
const reproducer = tracePracticeReproducer(turnId);
|
||||
const rawJson = useMemo(
|
||||
() => (evidence ? JSON.stringify(evidence, null, 2) : ""),
|
||||
[evidence],
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingState label="Loading sealed practice evidence..." />;
|
||||
}
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorState
|
||||
whatFailed={errorMessage(error)}
|
||||
mutationStatus="No trace mutation occurred."
|
||||
reproducer={reproducer}
|
||||
retrySafety="Retry: safe"
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!evidence) {
|
||||
return (
|
||||
<EmptyState
|
||||
statement="No sealed practice evidence recorded for this turn."
|
||||
nextAction={{ kind: "cli", command: reproducer }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const model = practiceEvidencePanelModel(evidence);
|
||||
return (
|
||||
<section className="grid gap-3" data-testid="practice-evidence-panel">
|
||||
{model.emptyMessage ? (
|
||||
<div className="rounded-md border border-[var(--color-state-warning-border)] bg-[var(--color-state-warning-bg)] p-3 text-sm text-[var(--color-state-warning-text)]">
|
||||
<h3 className="m-0 text-xs font-semibold uppercase">{model.status}</h3>
|
||||
<p className="m-0 mt-2">{model.emptyMessage}</p>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-3 xl:grid-cols-2">
|
||||
<MetadataTable
|
||||
rows={model.authorityRows.map((row) => ({
|
||||
key: row.key,
|
||||
value: rowValue(row.value),
|
||||
mono: true,
|
||||
}))}
|
||||
/>
|
||||
<MetadataTable
|
||||
rows={model.countRows.map((row) => ({
|
||||
key: row.key,
|
||||
value: rowValue(row.value),
|
||||
mono: true,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{model.chainRows.length > 0 ? (
|
||||
<MetadataTable
|
||||
rows={model.chainRows.map((row) => ({
|
||||
key: row.key,
|
||||
value: rowValue(row.value),
|
||||
mono: true,
|
||||
}))}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-3" data-testid="practice-evidence-details">
|
||||
{model.detailSections.map((section) => (
|
||||
<DetailSection key={section.title} section={section} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<section className="grid gap-2 rounded-md border border-[var(--color-border-subtle)] p-3">
|
||||
<h3 className="m-0 text-xs font-semibold uppercase text-[var(--color-text-secondary)]">
|
||||
Source spans
|
||||
</h3>
|
||||
{model.sourceSpanRows.length > 0 ? (
|
||||
<MetadataTable
|
||||
rows={model.sourceSpanRows.map((row) => ({
|
||||
key: row.key,
|
||||
value: rowValue(row.value),
|
||||
mono: true,
|
||||
}))}
|
||||
/>
|
||||
) : (
|
||||
<p className="m-0 text-sm text-[var(--color-text-secondary)]">
|
||||
No source spans recorded.
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<div>
|
||||
<div className="mb-1 text-xs font-semibold text-[var(--color-text-secondary)]">
|
||||
reproducer
|
||||
</div>
|
||||
<code className="block overflow-auto rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface-inset)] p-2 font-mono text-xs text-[var(--color-text-primary)]">
|
||||
{model.reproducer}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
{model.showRaw ? (
|
||||
<div className="max-h-[28rem] overflow-auto rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface-inset)] p-2">
|
||||
<StableJsonViewer source={rawJson} />
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
40
workbench-ui/src/app/trace/practiceEvidenceEndpoint.test.ts
Normal file
40
workbench-ui/src/app/trace/practiceEvidenceEndpoint.test.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { PracticeEvidence } from "../../types/practiceEvidence";
|
||||
import {
|
||||
fetchTracePracticeWith,
|
||||
tracePracticePath,
|
||||
tracePracticeReproducer,
|
||||
} from "./practiceEvidenceEndpoint";
|
||||
|
||||
const evidence: PracticeEvidence = {
|
||||
schema_version: "practice_evidence_v1",
|
||||
turn_id: 7,
|
||||
status: "missing_evidence",
|
||||
missing_reason: "sealed practice evidence was not persisted for this turn",
|
||||
record_kind: null,
|
||||
practice_disposition: null,
|
||||
chain: [],
|
||||
sealed_trace: null,
|
||||
trace_refusal: null,
|
||||
diagnostic_only: true,
|
||||
serving_allowed: false,
|
||||
mutation_allowed: false,
|
||||
replay_execution_allowed: false,
|
||||
replay_executed_by_workbench: false,
|
||||
};
|
||||
|
||||
describe("practice evidence endpoint helpers", () => {
|
||||
it("builds encoded trace practice paths", () => {
|
||||
expect(tracePracticePath(7)).toBe("/trace/7/practice");
|
||||
expect(tracePracticeReproducer(7)).toBe("curl /trace/7/practice");
|
||||
});
|
||||
|
||||
it("fetches practice evidence through an injected fetcher", async () => {
|
||||
const fetcher = vi.fn(<T,>(_path: string): Promise<T> => Promise.resolve(evidence as T));
|
||||
|
||||
await expect(
|
||||
fetchTracePracticeWith(7, fetcher as unknown as <T>(path: string) => Promise<T>),
|
||||
).resolves.toBe(evidence);
|
||||
expect(fetcher).toHaveBeenCalledWith("/trace/7/practice");
|
||||
});
|
||||
});
|
||||
20
workbench-ui/src/app/trace/practiceEvidenceEndpoint.ts
Normal file
20
workbench-ui/src/app/trace/practiceEvidenceEndpoint.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { PracticeEvidence } from "../../types/practiceEvidence";
|
||||
|
||||
export function tracePracticePath(turnId: number): string {
|
||||
return `/trace/${encodeURIComponent(String(turnId))}/practice`;
|
||||
}
|
||||
|
||||
export async function fetchTracePracticeWith(
|
||||
turnId: number,
|
||||
fetcher: <T>(path: string) => Promise<T>,
|
||||
): Promise<PracticeEvidence> {
|
||||
return fetcher<PracticeEvidence>(tracePracticePath(turnId));
|
||||
}
|
||||
|
||||
export const TRACE_PRACTICE_LOADING_LABEL = "Loading sealed practice evidence...";
|
||||
export const TRACE_PRACTICE_EMPTY_LABEL = "No sealed practice evidence recorded for this turn.";
|
||||
export const TRACE_PRACTICE_ERROR_MUTATION_STATUS = "No trace mutation occurred.";
|
||||
|
||||
export function tracePracticeReproducer(turnId: number): string {
|
||||
return `curl /trace/${encodeURIComponent(String(turnId))}/practice`;
|
||||
}
|
||||
187
workbench-ui/src/app/trace/practiceEvidencePanelModel.test.ts
Normal file
187
workbench-ui/src/app/trace/practiceEvidencePanelModel.test.ts
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import type { PracticeEvidence } from "../../types/practiceEvidence";
|
||||
import { practiceEvidencePanelModel } from "./practiceEvidencePanelModel";
|
||||
|
||||
const missingEvidence: PracticeEvidence = {
|
||||
schema_version: "practice_evidence_v1",
|
||||
turn_id: 7,
|
||||
status: "missing_evidence",
|
||||
missing_reason: "sealed practice evidence was not persisted for this turn",
|
||||
record_kind: null,
|
||||
practice_disposition: null,
|
||||
chain: [],
|
||||
sealed_trace: null,
|
||||
trace_refusal: null,
|
||||
diagnostic_only: true,
|
||||
serving_allowed: false,
|
||||
mutation_allowed: false,
|
||||
replay_execution_allowed: false,
|
||||
replay_executed_by_workbench: false,
|
||||
};
|
||||
|
||||
function sealedTraceEvidence(): PracticeEvidence {
|
||||
return {
|
||||
...missingEvidence,
|
||||
status: "recorded",
|
||||
missing_reason: null,
|
||||
record_kind: "sealed_trace",
|
||||
practice_disposition: "sealed",
|
||||
chain: [
|
||||
{
|
||||
kind: "problem_frame",
|
||||
status: "recorded",
|
||||
refs: ["pf_123"],
|
||||
summary: "Problem frame digest bound into the sealed practice trace.",
|
||||
},
|
||||
{
|
||||
kind: "geometric_search_run",
|
||||
status: "recorded",
|
||||
refs: ["gsr_1"],
|
||||
summary: "Geometric search run identity; Workbench does not execute search.",
|
||||
},
|
||||
{
|
||||
kind: "replay_results",
|
||||
status: "missing_evidence",
|
||||
refs: [],
|
||||
summary: "Replay adapter result identities; Workbench does not run replay here.",
|
||||
},
|
||||
],
|
||||
sealed_trace: {
|
||||
trace_id: "spt_1",
|
||||
trace_policy_version: "sealed-practice-v1",
|
||||
input_digest: "input_sha",
|
||||
problem_frame_digest: "pf_123",
|
||||
original_contract_assessment_id: "ca_1",
|
||||
residual_ids: ["res_1"],
|
||||
search_gate_decision_id: "sg_1",
|
||||
compute_budget_id: "budget_1",
|
||||
geometric_search_run_id: "gsr_1",
|
||||
candidate_attempt_ids: ["attempt_1"],
|
||||
candidate_attempt_binding_ids: ["binding_1"],
|
||||
replay_result_ids: [],
|
||||
replay_refusal_ids: ["rr_1"],
|
||||
upstream_identity_chain: ["pf_123", "ca_1", "res_1", "sg_1", "budget_1", "gsr_1"],
|
||||
practice_disposition: "sealed",
|
||||
trace_records: ["record_1"],
|
||||
evidence_spans: [{ text: "Lena has 3 marbles.", start: 0, end: 20, sentence_index: 0 }],
|
||||
created_by_policy: "sealed-practice-v1",
|
||||
explanation: "sealed diagnostic practice trace",
|
||||
},
|
||||
trace_refusal: null,
|
||||
};
|
||||
}
|
||||
|
||||
function refusalEvidence(): PracticeEvidence {
|
||||
return {
|
||||
...missingEvidence,
|
||||
status: "recorded",
|
||||
missing_reason: null,
|
||||
record_kind: "trace_refusal",
|
||||
practice_disposition: "refused",
|
||||
chain: [
|
||||
{
|
||||
kind: "trace_refusal",
|
||||
status: "recorded",
|
||||
refs: ["ptr_1"],
|
||||
summary: "Practice trace refused before a sealed practice trace could be projected.",
|
||||
},
|
||||
],
|
||||
trace_refusal: {
|
||||
trace_refusal_id: "ptr_1",
|
||||
trace_policy_version: "sealed-practice-v1",
|
||||
input_digest: null,
|
||||
practice_disposition: "refused",
|
||||
reason_codes: ["missing_residual"],
|
||||
explanation: "no residual target",
|
||||
},
|
||||
sealed_trace: null,
|
||||
};
|
||||
}
|
||||
|
||||
describe("practice evidence panel model", () => {
|
||||
it("models missing evidence honestly", () => {
|
||||
const model = practiceEvidencePanelModel(missingEvidence);
|
||||
|
||||
expect(model.status).toBe("missing_evidence");
|
||||
expect(model.emptyMessage).toBe("sealed practice evidence was not persisted for this turn");
|
||||
expect(model.reproducer).toBe("curl /trace/7/practice");
|
||||
expect(model.authorityRows).toEqual([
|
||||
{ key: "schema_version", value: "practice_evidence_v1" },
|
||||
{ key: "status", value: "missing_evidence" },
|
||||
{ key: "record_kind", value: "none" },
|
||||
{ key: "practice_disposition", value: "none" },
|
||||
{ key: "diagnostic_only", value: "true" },
|
||||
{ key: "serving_allowed", value: "false" },
|
||||
{ key: "mutation_allowed", value: "false" },
|
||||
{ key: "replay_execution_allowed", value: "false" },
|
||||
{ key: "replay_executed_by_workbench", value: "false" },
|
||||
{ key: "missing_reason", value: "sealed practice evidence was not persisted for this turn" },
|
||||
]);
|
||||
expect(model.countRows).toEqual([
|
||||
{ key: "chain_cards", value: "0" },
|
||||
{ key: "sealed_trace", value: "0" },
|
||||
{ key: "trace_refusal", value: "0" },
|
||||
{ key: "source_spans", value: "0" },
|
||||
]);
|
||||
expect(model.chainRows).toEqual([]);
|
||||
expect(model.sourceSpanRows).toEqual([]);
|
||||
expect(model.showRaw).toBe(false);
|
||||
});
|
||||
|
||||
it("models a recorded sealed practice trace without granting authority", () => {
|
||||
const model = practiceEvidencePanelModel(sealedTraceEvidence());
|
||||
|
||||
expect(model.emptyMessage).toBe(null);
|
||||
expect(model.showRaw).toBe(true);
|
||||
expect(model.countRows).toContainEqual({ key: "chain_cards", value: "3" });
|
||||
expect(model.chainRows).toEqual([
|
||||
{ key: "problem_frame", value: "recorded — pf_123" },
|
||||
{ key: "geometric_search_run", value: "recorded — gsr_1" },
|
||||
{ key: "replay_results", value: "missing_evidence — none" },
|
||||
]);
|
||||
expect(model.authorityRows).toContainEqual({ key: "diagnostic_only", value: "true" });
|
||||
expect(model.authorityRows).toContainEqual({ key: "serving_allowed", value: "false" });
|
||||
expect(model.authorityRows).toContainEqual({ key: "mutation_allowed", value: "false" });
|
||||
expect(model.authorityRows).toContainEqual({ key: "replay_execution_allowed", value: "false" });
|
||||
expect(model.authorityRows).toContainEqual({
|
||||
key: "replay_executed_by_workbench",
|
||||
value: "false",
|
||||
});
|
||||
|
||||
const chain = model.detailSections.find((section) => section.title === "Evidence chain");
|
||||
if (chain === undefined) throw new Error("missing Evidence chain section");
|
||||
const geometricSearchCard = chain.items.find((item) => item.title === "card 2: geometric_search_run");
|
||||
if (geometricSearchCard === undefined) throw new Error("missing geometric search card");
|
||||
expect(geometricSearchCard.rows).toContainEqual({
|
||||
key: "authority",
|
||||
value:
|
||||
"identity card only; Workbench does not execute search, replay, operators, sealing, or mutation",
|
||||
});
|
||||
|
||||
const sealed = model.detailSections.find((section) => section.title === "Sealed trace");
|
||||
if (sealed === undefined) throw new Error("missing Sealed trace section");
|
||||
const sealedTrace = sealed.items.find((item) => item.title === "spt_1");
|
||||
if (sealedTrace === undefined) throw new Error("missing sealed trace item");
|
||||
expect(sealedTrace.rows).toContainEqual({ key: "geometric_search_run_id", value: "gsr_1" });
|
||||
expect(sealedTrace.rows).toContainEqual({ key: "replay_refusal_ids", value: "rr_1" });
|
||||
expect(model.sourceSpanRows).toEqual([
|
||||
{ key: "sealed_trace.1", value: "0:20 Lena has 3 marbles. (sentence 0)" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("models practice trace refusals", () => {
|
||||
const model = practiceEvidencePanelModel(refusalEvidence());
|
||||
|
||||
expect(model.emptyMessage).toBe(null);
|
||||
expect(model.countRows).toContainEqual({ key: "trace_refusal", value: "1" });
|
||||
expect(model.chainRows).toEqual([{ key: "trace_refusal", value: "recorded — ptr_1" }]);
|
||||
|
||||
const refusal = model.detailSections.find((section) => section.title === "Trace refusal");
|
||||
if (refusal === undefined) throw new Error("missing Trace refusal section");
|
||||
const refusalItem = refusal.items.find((item) => item.title === "ptr_1");
|
||||
if (refusalItem === undefined) throw new Error("missing trace refusal item");
|
||||
expect(refusalItem.rows).toContainEqual({ key: "trace_refusal_id", value: "ptr_1" });
|
||||
expect(refusalItem.rows).toContainEqual({ key: "reason_codes", value: "missing_residual" });
|
||||
expect(refusalItem.rows).toContainEqual({ key: "explanation", value: "no residual target" });
|
||||
});
|
||||
});
|
||||
184
workbench-ui/src/app/trace/practiceEvidencePanelModel.ts
Normal file
184
workbench-ui/src/app/trace/practiceEvidencePanelModel.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import type {
|
||||
PracticeEvidence,
|
||||
PracticeEvidenceCard,
|
||||
PracticeSourceSpanView,
|
||||
SealedPracticeTraceView,
|
||||
PracticeTraceRefusalView,
|
||||
} from "../../types/practiceEvidence";
|
||||
import { tracePracticeReproducer } from "./practiceEvidenceEndpoint";
|
||||
import { practiceEvidenceEmptyMessage, practiceSourceSpanLabel } from "./practiceEvidenceView";
|
||||
|
||||
export interface PracticeEvidenceSummaryRow {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface PracticeEvidenceDetailItem {
|
||||
title: string;
|
||||
rows: PracticeEvidenceSummaryRow[];
|
||||
}
|
||||
|
||||
export interface PracticeEvidenceDetailSection {
|
||||
title: string;
|
||||
emptyMessage: string;
|
||||
items: PracticeEvidenceDetailItem[];
|
||||
}
|
||||
|
||||
export interface PracticeEvidencePanelModel {
|
||||
status: PracticeEvidence["status"];
|
||||
emptyMessage: string | null;
|
||||
reproducer: string;
|
||||
authorityRows: PracticeEvidenceSummaryRow[];
|
||||
countRows: PracticeEvidenceSummaryRow[];
|
||||
chainRows: PracticeEvidenceSummaryRow[];
|
||||
detailSections: PracticeEvidenceDetailSection[];
|
||||
sourceSpanRows: PracticeEvidenceSummaryRow[];
|
||||
showRaw: boolean;
|
||||
}
|
||||
|
||||
function boolLabel(value: boolean): string {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
|
||||
function listLabel(values: readonly string[] | null | undefined): string {
|
||||
return values && values.length > 0 ? values.join(", ") : "none";
|
||||
}
|
||||
|
||||
function sourceSpanRows(evidence: PracticeEvidence): PracticeEvidenceSummaryRow[] {
|
||||
const rows: PracticeEvidenceSummaryRow[] = [];
|
||||
const append = (prefix: string, spans: readonly PracticeSourceSpanView[] | null | undefined) => {
|
||||
if (!spans) return;
|
||||
spans.forEach((span, index) => {
|
||||
rows.push({ key: `${prefix}.${index + 1}`, value: practiceSourceSpanLabel(span) });
|
||||
});
|
||||
};
|
||||
if (evidence.sealed_trace != null) {
|
||||
append("sealed_trace", evidence.sealed_trace.evidence_spans);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function cardRows(card: PracticeEvidenceCard): PracticeEvidenceSummaryRow[] {
|
||||
return [
|
||||
{ key: "kind", value: card.kind },
|
||||
{ key: "status", value: card.status },
|
||||
{ key: "refs", value: listLabel(card.refs) },
|
||||
{ key: "summary", value: card.summary || "none" },
|
||||
{
|
||||
key: "authority",
|
||||
value: "identity card only; Workbench does not execute search, replay, operators, sealing, or mutation",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function sealedTraceRows(trace: SealedPracticeTraceView): PracticeEvidenceSummaryRow[] {
|
||||
return [
|
||||
{ key: "trace_id", value: trace.trace_id },
|
||||
{ key: "trace_policy_version", value: trace.trace_policy_version },
|
||||
{ key: "input_digest", value: trace.input_digest },
|
||||
{ key: "problem_frame_digest", value: trace.problem_frame_digest },
|
||||
{ key: "original_contract_assessment_id", value: trace.original_contract_assessment_id },
|
||||
{ key: "residual_ids", value: listLabel(trace.residual_ids) },
|
||||
{ key: "search_gate_decision_id", value: trace.search_gate_decision_id },
|
||||
{ key: "compute_budget_id", value: trace.compute_budget_id },
|
||||
{ key: "geometric_search_run_id", value: trace.geometric_search_run_id },
|
||||
{ key: "candidate_attempt_ids", value: listLabel(trace.candidate_attempt_ids) },
|
||||
{ key: "candidate_attempt_binding_ids", value: listLabel(trace.candidate_attempt_binding_ids) },
|
||||
{ key: "replay_result_ids", value: listLabel(trace.replay_result_ids) },
|
||||
{ key: "replay_refusal_ids", value: listLabel(trace.replay_refusal_ids) },
|
||||
{ key: "upstream_identity_chain", value: listLabel(trace.upstream_identity_chain) },
|
||||
{ key: "practice_disposition", value: trace.practice_disposition },
|
||||
{ key: "trace_records", value: listLabel(trace.trace_records) },
|
||||
{ key: "created_by_policy", value: trace.created_by_policy || "none" },
|
||||
{ key: "explanation", value: trace.explanation || "none" },
|
||||
];
|
||||
}
|
||||
|
||||
function traceRefusalRows(refusal: PracticeTraceRefusalView): PracticeEvidenceSummaryRow[] {
|
||||
return [
|
||||
{ key: "trace_refusal_id", value: refusal.trace_refusal_id },
|
||||
{ key: "trace_policy_version", value: refusal.trace_policy_version },
|
||||
{ key: "input_digest", value: refusal.input_digest ?? "none" },
|
||||
{ key: "practice_disposition", value: refusal.practice_disposition },
|
||||
{ key: "reason_codes", value: listLabel(refusal.reason_codes) },
|
||||
{ key: "explanation", value: refusal.explanation || "none" },
|
||||
];
|
||||
}
|
||||
|
||||
function detailSections(evidence: PracticeEvidence): PracticeEvidenceDetailSection[] {
|
||||
const chain = evidence.chain ?? [];
|
||||
return [
|
||||
{
|
||||
title: "Evidence chain",
|
||||
emptyMessage: "No sealed practice chain cards recorded.",
|
||||
items: chain.map((card, index) => ({
|
||||
title: `card ${index + 1}: ${card.kind}`,
|
||||
rows: cardRows(card),
|
||||
})),
|
||||
},
|
||||
{
|
||||
title: "Sealed trace",
|
||||
emptyMessage: "No sealed practice trace recorded.",
|
||||
items:
|
||||
evidence.sealed_trace == null
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: evidence.sealed_trace.trace_id,
|
||||
rows: sealedTraceRows(evidence.sealed_trace),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Trace refusal",
|
||||
emptyMessage: "No practice trace refusal recorded.",
|
||||
items:
|
||||
evidence.trace_refusal == null
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: evidence.trace_refusal.trace_refusal_id,
|
||||
rows: traceRefusalRows(evidence.trace_refusal),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function practiceEvidencePanelModel(evidence: PracticeEvidence): PracticeEvidencePanelModel {
|
||||
const chain = evidence.chain ?? [];
|
||||
const sealedTrace = evidence.sealed_trace;
|
||||
const traceRefusal = evidence.trace_refusal;
|
||||
const sourceSpanCount = sealedTrace?.evidence_spans?.length ?? 0;
|
||||
|
||||
return {
|
||||
status: evidence.status,
|
||||
emptyMessage: practiceEvidenceEmptyMessage(evidence),
|
||||
reproducer: tracePracticeReproducer(evidence.turn_id),
|
||||
authorityRows: [
|
||||
{ key: "schema_version", value: evidence.schema_version },
|
||||
{ key: "status", value: evidence.status },
|
||||
{ key: "record_kind", value: evidence.record_kind ?? "none" },
|
||||
{ key: "practice_disposition", value: evidence.practice_disposition ?? "none" },
|
||||
{ key: "diagnostic_only", value: boolLabel(evidence.diagnostic_only) },
|
||||
{ key: "serving_allowed", value: boolLabel(evidence.serving_allowed) },
|
||||
{ key: "mutation_allowed", value: boolLabel(evidence.mutation_allowed) },
|
||||
{ key: "replay_execution_allowed", value: boolLabel(evidence.replay_execution_allowed) },
|
||||
{ key: "replay_executed_by_workbench", value: boolLabel(evidence.replay_executed_by_workbench) },
|
||||
{ key: "missing_reason", value: evidence.missing_reason ?? "none" },
|
||||
],
|
||||
countRows: [
|
||||
{ key: "chain_cards", value: String(chain.length) },
|
||||
{ key: "sealed_trace", value: sealedTrace == null ? "0" : "1" },
|
||||
{ key: "trace_refusal", value: traceRefusal == null ? "0" : "1" },
|
||||
{ key: "source_spans", value: String(sourceSpanCount) },
|
||||
],
|
||||
chainRows: chain.map((card) => ({
|
||||
key: card.kind,
|
||||
value: `${card.status} — ${listLabel(card.refs)}`,
|
||||
})),
|
||||
detailSections: detailSections(evidence),
|
||||
sourceSpanRows: sourceSpanRows(evidence),
|
||||
showRaw: evidence.status === "recorded",
|
||||
};
|
||||
}
|
||||
27
workbench-ui/src/app/trace/practiceEvidenceView.ts
Normal file
27
workbench-ui/src/app/trace/practiceEvidenceView.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import type { PracticeEvidence, PracticeSourceSpanView } from "../../types/practiceEvidence";
|
||||
|
||||
export const PRACTICE_AUTHORITY_DISCLOSURES = [
|
||||
"Diagnostic Only",
|
||||
"Serving Disallowed",
|
||||
"Mutation Disallowed",
|
||||
"Replay Execution Disallowed",
|
||||
"Workbench Did Not Execute Replay",
|
||||
] as const;
|
||||
|
||||
export function practiceEvidenceEmptyMessage(evidence: PracticeEvidence): string | null {
|
||||
if (evidence.status === "missing_evidence") {
|
||||
return evidence.missing_reason ?? "No sealed practice evidence recorded for this turn.";
|
||||
}
|
||||
const chain = evidence.chain ?? [];
|
||||
return chain.length > 0 || evidence.sealed_trace != null || evidence.trace_refusal != null
|
||||
? null
|
||||
: "No sealed practice evidence recorded for this turn.";
|
||||
}
|
||||
|
||||
export function practiceSourceSpanLabel(span: PracticeSourceSpanView): string {
|
||||
const sentence =
|
||||
span.sentence_index === null || span.sentence_index === undefined
|
||||
? "sentence unknown"
|
||||
: `sentence ${span.sentence_index}`;
|
||||
return `${span.start}:${span.end} ${span.text} (${sentence})`;
|
||||
}
|
||||
78
workbench-ui/src/types/practiceEvidence.ts
Normal file
78
workbench-ui/src/types/practiceEvidence.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
export type PracticeEvidenceStatus = "recorded" | "missing_evidence";
|
||||
export type PracticeRecordKind = "sealed_trace" | "trace_refusal" | null;
|
||||
|
||||
export type PracticeCardKind =
|
||||
| "problem_frame"
|
||||
| "contract_assessment"
|
||||
| "residuals"
|
||||
| "search_gate"
|
||||
| "compute_budget"
|
||||
| "geometric_search_run"
|
||||
| "candidate_attempts"
|
||||
| "attempt_bindings"
|
||||
| "replay_results"
|
||||
| "replay_refusals"
|
||||
| "sealed_trace"
|
||||
| "trace_refusal";
|
||||
|
||||
export interface PracticeSourceSpanView {
|
||||
text: string;
|
||||
start: number;
|
||||
end: number;
|
||||
sentence_index: number | null;
|
||||
}
|
||||
|
||||
export interface PracticeEvidenceCard {
|
||||
kind: PracticeCardKind;
|
||||
status: PracticeEvidenceStatus;
|
||||
refs: string[];
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface SealedPracticeTraceView {
|
||||
trace_id: string;
|
||||
trace_policy_version: string;
|
||||
input_digest: string;
|
||||
problem_frame_digest: string;
|
||||
original_contract_assessment_id: string;
|
||||
residual_ids: string[];
|
||||
search_gate_decision_id: string;
|
||||
compute_budget_id: string;
|
||||
geometric_search_run_id: string;
|
||||
candidate_attempt_ids: string[];
|
||||
candidate_attempt_binding_ids: string[];
|
||||
replay_result_ids: string[];
|
||||
replay_refusal_ids: string[];
|
||||
upstream_identity_chain: string[];
|
||||
practice_disposition: string;
|
||||
trace_records: string[];
|
||||
evidence_spans: PracticeSourceSpanView[];
|
||||
created_by_policy: string;
|
||||
explanation: string;
|
||||
}
|
||||
|
||||
export interface PracticeTraceRefusalView {
|
||||
trace_refusal_id: string;
|
||||
trace_policy_version: string;
|
||||
input_digest: string | null;
|
||||
practice_disposition: string;
|
||||
reason_codes: string[];
|
||||
explanation: string;
|
||||
}
|
||||
|
||||
export interface PracticeEvidence {
|
||||
schema_version: "practice_evidence_v1";
|
||||
turn_id: number;
|
||||
status: PracticeEvidenceStatus;
|
||||
missing_reason: string | null;
|
||||
record_kind: PracticeRecordKind;
|
||||
practice_disposition: string | null;
|
||||
chain: PracticeEvidenceCard[];
|
||||
sealed_trace: SealedPracticeTraceView | null;
|
||||
trace_refusal: PracticeTraceRefusalView | null;
|
||||
diagnostic_only: boolean;
|
||||
serving_allowed: boolean;
|
||||
mutation_allowed: boolean;
|
||||
replay_execution_allowed: boolean;
|
||||
replay_executed_by_workbench: boolean;
|
||||
}
|
||||
|
|
@ -55,6 +55,8 @@ CRITICAL_FIELDS = frozenset(
|
|||
"field_evidence",
|
||||
"checkpoint_emitted",
|
||||
"trace_integrity",
|
||||
"construction_evidence",
|
||||
"practice_evidence",
|
||||
}
|
||||
)
|
||||
# Wall-clock by nature, or derived over wall-clock bytes (journal_digest
|
||||
|
|
|
|||
Loading…
Reference in a new issue