core/workbench-ui/src/app/evidenceContext.tsx
Shay 74dc7c7a3e
workbench(vault): deepen vault entry inspector + raw metadata drawer (#762)
Surface a selected vault entry's real evidence instead of just
epistemic_state + a digest:

- widen VaultEntrySubjectData to carry epistemic_status + the full
  metadata dict (data already reaches the inspector at runtime; this is
  a type-only unlock, no new fetch/endpoint)
- inspector shows epistemic_status and epistemic_state distinctly, core
  identity rows (turn, role) with honest 'not recorded' when absent,
  present-only rows (corrected, energy_*, promotion_certificate_digest),
  a propositional_form headline when present, copyable vault:<index> and
  versor_digest handles, and a collapsible key-sorted raw metadata drawer
- curated rows key off fields that actually exist on vault entries; the
  raw drawer is the catch-all as the open metadata schema grows

Exact-recall doctrine preserved: no similarity/relevance/score text.
2026-06-15 01:29:12 -07:00

190 lines
5.1 KiB
TypeScript

import {
createContext,
useContext,
useState,
useCallback,
type ReactNode,
} from "react";
import { getWorkbenchPrefs } from "./workbenchPrefs";
import type {
ChatTurnResult,
TurnJournalEntry,
ProposalDetail,
MathProposalDetail,
ArtifactDetail,
EvalRunResult,
CalibrationClass,
LogosPackOverview,
LogosLexiconRow,
LogosGlossRow,
LogosMorphologyRow,
LogosAlignmentRow,
SafetyVerdict,
} from "../types/api";
export type ProposalSubjectDomain = "cognition" | "math";
export interface RunSubjectData {
session_id?: string;
source?: string;
checkpoint_present?: boolean;
checkpoint_revision?: string | null;
evidence_gap?: string | null;
}
export interface PackSubjectData {
pack_id?: string;
checksum?: string | null;
manifest_digest?: string | null;
determinism_class?: string | null;
}
export interface LogosPackSubjectData
extends Partial<
Pick<
LogosPackOverview,
| "pack_id"
| "language"
| "role"
| "script"
| "version"
| "determinism_class"
| "gate_engaged"
| "oov_policy"
| "safety_status"
| "manifest_digest"
| "manifest_path"
| "holonomy_case_count"
>
> {
checksum_status?: SafetyVerdict | null;
}
export interface VaultEntrySubjectData {
entry_index?: number;
epistemic_status?: string;
epistemic_state?: string;
// The full open metadata dict the reader passes through. Already carried at
// runtime (selectEntry sets the complete VaultEntry); typed here so the
// inspector can surface it. Curated rows key off fields that actually exist;
// the raw drawer renders the rest.
metadata?: Record<string, unknown>;
versor_digest?: string | null;
}
export interface AuditEventSubjectData {
event_id?: string;
mutation_boundary?: boolean;
payload_digest?: string | null;
}
// `data` is optional: a subject restored from a URL carries identity only
// until the owning route's query loads its detail. Inspectors must render
// an honest "detail not loaded" state when data is absent.
export type EvidenceSubject =
| { kind: "turn"; turnId: number; data?: ChatTurnResult | TurnJournalEntry }
| {
kind: "proposal";
proposalId: string;
domain?: ProposalSubjectDomain;
data?: ProposalDetail | MathProposalDetail;
}
| { kind: "artifact"; artifactId: string; data?: ArtifactDetail }
| { kind: "eval_result"; lane: string; data?: EvalRunResult }
| { kind: "run"; sessionId: string; data?: RunSubjectData }
| { kind: "pack"; packId: string; data?: PackSubjectData }
| { kind: "logos_pack"; packId: string; data?: LogosPackSubjectData }
| {
kind: "logos_entry";
packId: string;
entryId: string;
data?: LogosLexiconRow;
}
| {
kind: "logos_gloss";
packId: string;
glossId: string;
data?: LogosGlossRow;
}
| {
kind: "logos_morphology";
packId: string;
morphologyId: string;
data?: LogosMorphologyRow;
}
| {
kind: "logos_alignment_edge";
packId: string;
edgeId: string;
data?: LogosAlignmentRow;
}
| { kind: "vault_entry"; entryIndex: number; data?: VaultEntrySubjectData }
| { kind: "audit_event"; eventId: string; data?: AuditEventSubjectData }
| { kind: "calibration_class"; className: string; data?: CalibrationClass }
| { kind: "none" };
interface EvidenceContextValue {
subject: EvidenceSubject;
setSubject: (subject: EvidenceSubject) => void;
clearSubject: () => void;
inspectorOpen: boolean;
setInspectorOpen: (open: boolean) => void;
toggleInspector: () => void;
addressCopyCount: number;
notifyAddressCopied: () => void;
}
const NONE_SUBJECT: EvidenceSubject = { kind: "none" };
const EvidenceContext = createContext<EvidenceContextValue | null>(null);
export function EvidenceProvider({ children }: { children: ReactNode }) {
const [subject, setSubjectState] = useState<EvidenceSubject>(NONE_SUBJECT);
// Initial visibility follows the workbench pref; EvidenceUrlSync still
// force-opens on a `?inspect=` deep link.
const [inspectorOpen, setInspectorOpen] = useState(
() => getWorkbenchPrefs().inspectorDefaultOpen,
);
const [addressCopyCount, setAddressCopyCount] = useState(0);
const setSubject = useCallback((s: EvidenceSubject) => {
setSubjectState(s);
}, []);
const clearSubject = useCallback(() => {
setSubjectState(NONE_SUBJECT);
}, []);
const toggleInspector = useCallback(() => {
setInspectorOpen((prev) => !prev);
}, []);
const notifyAddressCopied = useCallback(() => {
setAddressCopyCount((prev) => prev + 1);
}, []);
return (
<EvidenceContext.Provider
value={{
subject,
setSubject,
clearSubject,
inspectorOpen,
setInspectorOpen,
toggleInspector,
addressCopyCount,
notifyAddressCopied,
}}
>
{children}
</EvidenceContext.Provider>
);
}
export function useEvidenceSubject(): EvidenceContextValue {
const ctx = useContext(EvidenceContext);
if (!ctx) {
throw new Error("useEvidenceSubject must be used within EvidenceProvider");
}
return ctx;
}