From 74dc7c7a3e6ab275875a184c4dd4898caa2dfe56 Mon Sep 17 00:00:00 2001 From: Shay Date: Mon, 15 Jun 2026 01:29:12 -0700 Subject: [PATCH] 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: 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. --- workbench-ui/src/app/RightInspector.test.tsx | 79 +++++++++++++ workbench-ui/src/app/RightInspector.tsx | 117 ++++++++++++++++--- workbench-ui/src/app/evidenceContext.tsx | 6 + 3 files changed, 189 insertions(+), 13 deletions(-) diff --git a/workbench-ui/src/app/RightInspector.test.tsx b/workbench-ui/src/app/RightInspector.test.tsx index b5e3b9f7..74452afe 100644 --- a/workbench-ui/src/app/RightInspector.test.tsx +++ b/workbench-ui/src/app/RightInspector.test.tsx @@ -126,6 +126,85 @@ describe("RightInspector", () => { expect(screen.getByText("sha256:practice")).toBeInTheDocument(); }); + it("shows vault entry depth: distinct status/state, curated metadata, handle, raw drawer", () => { + function SetVaultEntry() { + const { setSubject } = useEvidenceSubject(); + useEffect(() => { + setSubject({ + kind: "vault_entry", + entryIndex: 7, + data: { + entry_index: 7, + epistemic_status: "coherent", + epistemic_state: "decoded", + versor_digest: "sha256:vvvvvvvvvvvv", + metadata: { + turn: 3, + role: "assistant", + energy_class: "warm", + coherence_residual: 0.01, + propositional_form: "alpha causes beta", + promotion_certificate_digest: "sha256:certcertcert", + }, + }, + }); + }, [setSubject]); + return ; + } + render( + + + , + ); + + expect(screen.getByText("Vault Entry")).toBeInTheDocument(); + expect(screen.getByText("#7")).toBeInTheDocument(); + // epistemic_status (tier) and epistemic_state (trust display) are distinct + expect(screen.getByText("epistemic_status")).toBeInTheDocument(); + expect(screen.getByText("epistemic_state")).toBeInTheDocument(); + expect(screen.getByText("coherent")).toBeInTheDocument(); + expect(screen.getByText("decoded")).toBeInTheDocument(); + // core + present-only metadata rows + expect(screen.getByText("role")).toBeInTheDocument(); + expect(screen.getByText("assistant")).toBeInTheDocument(); + expect(screen.getByText("energy_class")).toBeInTheDocument(); + expect(screen.getByText("promotion_certificate_digest")).toBeInTheDocument(); + // propositional_form headline + expect(screen.getByText("alpha causes beta")).toBeInTheDocument(); + // copyable evidence-address handle + expect(screen.getByText("vault:7")).toBeInTheDocument(); + // raw metadata drawer present + expect(screen.getByText("Raw metadata")).toBeInTheDocument(); + // exact-recall doctrine: never a similarity/relevance proxy + expect(screen.queryByText(/similarity|relevance|score/i)).not.toBeInTheDocument(); + }); + + it("renders 'not recorded' for absent core vault fields, not silent disappearance", () => { + function SetSparseVaultEntry() { + const { setSubject } = useEvidenceSubject(); + useEffect(() => { + setSubject({ + kind: "vault_entry", + entryIndex: 9, + data: { entry_index: 9, versor_digest: null }, + }); + }, [setSubject]); + return ; + } + render( + + + , + ); + + expect(screen.getByText("Vault Entry")).toBeInTheDocument(); + // epistemic_status, epistemic_state, turn, role all absent → 4 "not recorded" + expect(screen.getAllByText("not recorded").length).toBe(4); + // handle still present; no raw drawer when metadata is absent + expect(screen.getByText("vault:9")).toBeInTheDocument(); + expect(screen.queryByText("Raw metadata")).not.toBeInTheDocument(); + }); + it("shows a transient Copied confirmation after an address copy", () => { vi.useFakeTimers(); try { diff --git a/workbench-ui/src/app/RightInspector.tsx b/workbench-ui/src/app/RightInspector.tsx index 3d908071..56e6b282 100644 --- a/workbench-ui/src/app/RightInspector.tsx +++ b/workbench-ui/src/app/RightInspector.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { useEvidenceSubject, type EvidenceSubject } from "./evidenceContext"; import { EvidenceChainRail } from "./EvidenceChainRail"; -import { MetadataTable } from "../design/components/MetadataTable/MetadataTable"; +import { MetadataTable, type MetadataRow } from "../design/components/MetadataTable/MetadataTable"; import { DigestBadge } from "../design/components/DigestBadge/DigestBadge"; import { Timestamp } from "../design/components/Timestamp/Timestamp"; import { @@ -465,24 +465,115 @@ function LogosAlignmentEdgeInspector({ ); } +// Honest absence for a core identity field that every session entry should +// carry — shown rather than silently dropped. +const NOT_RECORDED = "not recorded"; + +// Read one metadata field as a display string, or null when genuinely absent. +// The vault metadata dict is open; values are strings/numbers/booleans (turn, +// role, energy_*, corrected, propositional_form, ...). +function vaultMetaValue( + metadata: Record | undefined, + key: string, +): string | null { + if (!metadata) return null; + const value = metadata[key]; + if (value === null || value === undefined) return null; + if (typeof value === "string") return value; + if (typeof value === "number" || typeof value === "boolean") return String(value); + return JSON.stringify(value); +} + +// Deterministic, recursively key-sorted JSON for the raw drawer — same bytes +// every render regardless of backend key order, so the drawer never churns. +function stableJson(value: unknown): string { + return JSON.stringify( + value, + (_key, val) => + val && typeof val === "object" && !Array.isArray(val) + ? Object.fromEntries( + Object.keys(val as Record) + .sort() + .map((k) => [k, (val as Record)[k]]), + ) + : val, + 2, + ); +} + +// Optional rows surfaced only when the (open) metadata actually carries them — +// these don't exist on every entry, so absence is correct, not "not recorded". +const VAULT_OPTIONAL_FIELDS: { key: string; mono?: boolean; copyable?: boolean }[] = [ + { key: "corrected" }, + { key: "energy_class" }, + { key: "energy_raw", mono: true }, + { key: "coherence_residual", mono: true }, + { key: "promotion_certificate_digest", mono: true, copyable: true }, +]; + function VaultEntryInspector({ subject }: { subject: Extract }) { const { data } = subject; + const handle = `vault:${subject.entryIndex}`; + if (!data) { + return ( +
+

Vault Entry

+

#{subject.entryIndex}

+ +
+ ); + } + + const metadata = data.metadata; + // Headline only a string propositional_form; structured forms stay in the drawer. + const propositionalForm = + metadata && typeof metadata.propositional_form === "string" + ? metadata.propositional_form + : null; + + // Core identity rows — always shown, with honest "not recorded" when absent. + // epistemic_status (storage tier) and epistemic_state (trust display) are + // distinct fields and must read distinctly. + const coreRows: MetadataRow[] = [ + { key: "epistemic_status", value: data.epistemic_status ?? NOT_RECORDED }, + { key: "epistemic_state", value: data.epistemic_state ?? NOT_RECORDED }, + { key: "turn", value: vaultMetaValue(metadata, "turn") ?? NOT_RECORDED, mono: true }, + { key: "role", value: vaultMetaValue(metadata, "role") ?? NOT_RECORDED }, + ]; + + const optionalRows: MetadataRow[] = VAULT_OPTIONAL_FIELDS.flatMap(({ key, mono, copyable }) => { + const value = vaultMetaValue(metadata, key); + return value === null ? [] : [{ key, value, mono, copyable }]; + }); + + // Copyable handles: the evidence address and the versor digest. + const handleRows: MetadataRow[] = [ + { key: "handle", value: handle, mono: true, copyable: true }, + ...(data.versor_digest + ? [{ key: "versor_digest", value: data.versor_digest, mono: true, copyable: true } as const] + : []), + ]; + return (

Vault Entry

#{subject.entryIndex}

- {data ? ( - - ) : ( - - )} + {propositionalForm ? ( +

+ {propositionalForm} +

+ ) : null} + + {metadata && Object.keys(metadata).length > 0 ? ( +
+ + Raw metadata + +
+            {stableJson(metadata)}
+          
+
+ ) : null}
); } diff --git a/workbench-ui/src/app/evidenceContext.tsx b/workbench-ui/src/app/evidenceContext.tsx index 62aebdaf..64d16aa8 100644 --- a/workbench-ui/src/app/evidenceContext.tsx +++ b/workbench-ui/src/app/evidenceContext.tsx @@ -62,7 +62,13 @@ export interface LogosPackSubjectData 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; versor_digest?: string | null; }