core/workbench-ui/src/api/queries.ts
Shay 4d8aa1dac9 feat(workbench): the Replay Moment — turn-keyed hero + retire W-026 artifact replay (Wave R3)
First R3 (Theater) piece. Reworks the stale artifact-keyed ReplayRoute into
the turn-keyed hero over the #716 sealed-replay backend, and fully retires
the dead W-026 artifact-keyed machinery on both sides (the NOT_YET_MIRRORED
comment anticipated this).

The hero makes determinism *felt*: pick a journaled turn, CORE re-executes
its prompt in a sealed fresh runtime, and the result renders as a hash
verdict (≡ equivalent / ≠ diverged) + the original/replay DigestBadges + a
leaf diff (critical weighted above informational, each with a ≠ glyph). The
'What this proves' card surfaces comparison_basis + origin_state and states
the honest limit: a divergence means nondeterminism OR origin-state
influence, never rendered on its own as a determinism failure.

Retirement (verified zero serving uses):
- Python schemas.py: removed ReplayComparison/ReplayDivergence/ReplayStatus/
  ReplayDivergenceSeverity; scripts/dump-enums.py drops the two replay enums;
  both snapshots regenerated (deterministic: dump == committed)
- TS: removed ReplayComparison/ReplayDivergence/ReplayDivergenceSeverity;
  added TurnReplayComparison/TurnReplayDivergence (+ basis/origin/severity
  unions); NOT_YET_MIRRORED now empty (every engine schema mirrored)
- badges: removed ReplayStatusBadge + ReplayDivergenceSeverityBadge +
  meta/enums + their enumCoverage cases (hero renders severity inline)
- api: fetchReplayComparison/useReplayComparison -> fetchTurnReplay/
  useTurnReplay (/replay/<turnId>)
- deleted ArtifactList, ReplayComparisonPanel, ReplayDiffViewer,
  ReplayMetadataTable, old replay.test.tsx
- App route /replay/:artifactId? -> /replay/:turnId?; conformance row
  turn-keyed (loading 'Loading turns...', empty 'No turns recorded yet.')

Tests: ReplayRoute.test.tsx (equivalence hero + honesty card, diverged
critical leaf, informational-only label, replace-mode select, j/k spine).
Full vitest 358 green; workbench Python 34 green; both snapshots deterministic.

Follow-up flagged (not in this PR to keep it focused): the artifact query
hooks (useArtifacts/useArtifactDetail/fetchArtifacts*) are now orphaned but
entangled with the still-live artifact EvidenceSubject kind — a separate
cleanup once an Artifacts route or that subject's fate is decided.

Brief: docs/handoff/wave-R3-briefs-2026-06-13.md (all four R3 pieces).
2026-06-12 22:07:34 -07:00

325 lines
9.2 KiB
TypeScript

import {
QueryClient,
useQuery,
useMutation,
QueryClientProvider,
} from "@tanstack/react-query";
import {
apiFetch,
fetchEvalLanes,
runEvalLane,
fetchArtifacts,
fetchArtifactDetail,
fetchTurnReplay,
fetchProposalDetail,
fetchProposals,
fetchAuditEvents,
fetchRuns,
fetchRun,
fetchPacks,
fetchPack,
fetchVaultSummary,
fetchVaultEntries,
fetchTraceTurn,
fetchTraceTurns,
fetchMathProposals,
fetchMathProposalDetail,
ratifyMathProposal,
rejectMathProposal,
deferMathProposal,
type ProposalStateFilter,
} from "./client";
import type { WorkbenchApiError } from "./client";
import type {
RuntimeStatus,
ArtifactRef,
ArtifactDetail,
ProposalSummary,
ProposalDetail,
AuditEvent,
RunSummary,
RunDetail,
PackSummary,
PackDetail,
VaultSummary,
VaultEntry,
TurnJournalEntry,
TurnJournalSummary,
EvalLaneSummary,
EvalRunResult,
ChatTurnResult,
EvalRunRequest,
TurnReplayComparison,
MathProposalSummary,
MathProposalDetail,
MathRatifyResult,
} from "../types/api";
export { QueryClientProvider };
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5000,
refetchOnWindowFocus: false,
},
},
});
export function useRuntimeStatus() {
return useQuery<RuntimeStatus>({
queryKey: ["api", "runtime-status"],
queryFn: () => apiFetch<RuntimeStatus>("/runtime/status"),
refetchInterval: 30_000,
});
}
export function useArtifacts() {
return useQuery<ArtifactRef[], WorkbenchApiError>({
queryKey: ["api", "artifacts"],
queryFn: () => fetchArtifacts(),
staleTime: 60000,
refetchOnWindowFocus: false,
});
}
export function useArtifact(id: string) {
return useQuery<ArtifactDetail, WorkbenchApiError>({
queryKey: ["api", "artifact", id],
queryFn: () => fetchArtifactDetail(id),
enabled: !!id,
});
}
export function useArtifactDetail(artifactId: string) {
return useQuery<ArtifactDetail, WorkbenchApiError>({
queryKey: ["api", "artifact", artifactId],
queryFn: () => fetchArtifactDetail(artifactId),
enabled: !!artifactId,
});
}
export function useTurnReplay(turnId?: number | null) {
return useQuery<TurnReplayComparison, WorkbenchApiError>({
queryKey: ["api", "replay", "turn", turnId ?? null],
queryFn: () => fetchTurnReplay(turnId as number),
enabled: typeof turnId === "number",
retry: false,
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function useProposals(filter: ProposalStateFilter = "all") {
return useQuery<ProposalSummary[]>({
queryKey: ["api", "proposals", filter],
queryFn: () => fetchProposals(filter),
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function useProposalDetail(proposalId: string) {
return useQuery<ProposalDetail>({
queryKey: ["api", "proposal", proposalId],
queryFn: () => fetchProposalDetail(proposalId),
enabled: !!proposalId,
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function useTraceTurns(limit?: number, offset?: number) {
return useQuery<TurnJournalSummary[], WorkbenchApiError>({
queryKey: ["api", "trace", "turns", limit ?? null, offset ?? null],
queryFn: () => fetchTraceTurns(limit, offset),
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function useTraceTurn(turnId?: number | null) {
return useQuery<TurnJournalEntry, WorkbenchApiError>({
queryKey: ["api", "trace", "turn", turnId ?? null],
queryFn: () => fetchTraceTurn(turnId as number),
enabled: typeof turnId === "number",
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function useAuditEvents(limit?: number, offset?: number) {
return useQuery<{ items: AuditEvent[] }, WorkbenchApiError>({
queryKey: ["api", "audit", "events", limit ?? null, offset ?? null],
queryFn: () => fetchAuditEvents(limit, offset),
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function useRuns(limit?: number, offset?: number) {
return useQuery<RunSummary[], WorkbenchApiError>({
queryKey: ["api", "runs", limit ?? null, offset ?? null],
queryFn: () => fetchRuns(limit, offset),
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function useRun(sessionId?: string | null, turnLimit?: number) {
return useQuery<RunDetail, WorkbenchApiError>({
queryKey: ["api", "run", sessionId ?? null, turnLimit ?? null],
queryFn: () => fetchRun(sessionId as string, turnLimit),
enabled: typeof sessionId === "string" && sessionId.length > 0,
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function useEvalLanes() {
return useQuery<EvalLaneSummary[]>({
queryKey: ["api", "evals"],
queryFn: fetchEvalLanes,
staleTime: 60_000,
refetchOnWindowFocus: false,
});
}
export function useEvalLane(name: string) {
return useQuery<EvalRunResult>({
queryKey: ["api", "eval", name],
queryFn: () => apiFetch<EvalRunResult>(`/evals/${name}`),
enabled: !!name,
});
}
export function useEvalRun() {
return useMutation<EvalRunResult, WorkbenchApiError, EvalRunRequest>({
mutationKey: ["eval-run"],
mutationFn: runEvalLane,
});
}
export function useChatTurn() {
return useMutation<ChatTurnResult, WorkbenchApiError, { prompt: string }>({
mutationKey: ["chat-turn"],
mutationFn: ({ prompt }) =>
apiFetch<ChatTurnResult>("/chat/turn", {
method: "POST",
body: JSON.stringify({ prompt }),
headers: { "Content-Type": "application/json" },
}),
});
}
export function useMathProposals() {
return useQuery<MathProposalSummary[]>({
queryKey: ["api", "math-proposals"],
queryFn: fetchMathProposals,
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function useMathProposalDetail(proposalId: string) {
return useQuery<MathProposalDetail>({
queryKey: ["api", "math-proposal", proposalId],
queryFn: () => fetchMathProposalDetail(proposalId),
enabled: !!proposalId,
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function useMathRatify() {
return useMutation<
MathRatifyResult,
WorkbenchApiError,
{ proposalId: string; category?: string; polarity?: string; dryRun?: boolean }
>({
mutationKey: ["math-ratify"],
mutationFn: ({ proposalId, category, polarity, dryRun }) =>
ratifyMathProposal(proposalId, category, polarity, dryRun),
onSuccess: (data, variables) => {
queryClient.invalidateQueries({ queryKey: ["api", "proposals"] });
queryClient.invalidateQueries({ queryKey: ["api", "math-proposals"] });
queryClient.invalidateQueries({ queryKey: ["api", "proposal", variables.proposalId] });
queryClient.invalidateQueries({ queryKey: ["api", "math-proposal", variables.proposalId] });
},
});
}
export function useMathReject() {
return useMutation<
{ proposal_id: string; rejected: boolean },
WorkbenchApiError,
{ proposalId: string; note?: string }
>({
mutationKey: ["math-reject"],
mutationFn: ({ proposalId, note }) => rejectMathProposal(proposalId, note),
onSuccess: (data, variables) => {
queryClient.invalidateQueries({ queryKey: ["api", "proposals"] });
queryClient.invalidateQueries({ queryKey: ["api", "math-proposals"] });
queryClient.invalidateQueries({ queryKey: ["api", "proposal", variables.proposalId] });
queryClient.invalidateQueries({ queryKey: ["api", "math-proposal", variables.proposalId] });
},
});
}
export function useMathDefer() {
return useMutation<
{ proposal_id: string; deferred: boolean },
WorkbenchApiError,
{ proposalId: string }
>({
mutationKey: ["math-defer"],
mutationFn: ({ proposalId }) => deferMathProposal(proposalId),
onSuccess: (data, variables) => {
queryClient.invalidateQueries({ queryKey: ["api", "proposals"] });
queryClient.invalidateQueries({ queryKey: ["api", "math-proposals"] });
queryClient.invalidateQueries({ queryKey: ["api", "proposal", variables.proposalId] });
queryClient.invalidateQueries({ queryKey: ["api", "math-proposal", variables.proposalId] });
},
});
}
export function usePacks(limit?: number, offset?: number) {
return useQuery<PackSummary[], WorkbenchApiError>({
queryKey: ["api", "packs", limit ?? null, offset ?? null],
queryFn: () => fetchPacks(limit, offset),
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function usePack(packId?: string | null) {
return useQuery<PackDetail, WorkbenchApiError>({
queryKey: ["api", "pack", packId ?? null],
queryFn: () => fetchPack(packId as string),
enabled: typeof packId === "string" && packId.length > 0,
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function useVaultSummary() {
return useQuery<VaultSummary, WorkbenchApiError>({
queryKey: ["api", "vault", "summary"],
queryFn: () => fetchVaultSummary(),
retry: false,
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}
export function useVaultEntries(enabled: boolean, limit?: number, offset?: number) {
return useQuery<VaultEntry[], WorkbenchApiError>({
queryKey: ["api", "vault", "entries", limit ?? null, offset ?? null],
queryFn: () => fetchVaultEntries(limit, offset),
enabled,
retry: false,
staleTime: 30_000,
refetchOnWindowFocus: false,
});
}