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.
This commit is contained in:
parent
4522e5b1cf
commit
74dc7c7a3e
3 changed files with 189 additions and 13 deletions
|
|
@ -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 <RightInspector />;
|
||||
}
|
||||
render(
|
||||
<EvidenceProvider>
|
||||
<SetVaultEntry />
|
||||
</EvidenceProvider>,
|
||||
);
|
||||
|
||||
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 <RightInspector />;
|
||||
}
|
||||
render(
|
||||
<EvidenceProvider>
|
||||
<SetSparseVaultEntry />
|
||||
</EvidenceProvider>,
|
||||
);
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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<string, unknown> | 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<string, unknown>)
|
||||
.sort()
|
||||
.map((k) => [k, (val as Record<string, unknown>)[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<EvidenceSubject, { kind: "vault_entry" }> }) {
|
||||
const { data } = subject;
|
||||
const handle = `vault:${subject.entryIndex}`;
|
||||
if (!data) {
|
||||
return (
|
||||
<div className="grid gap-3">
|
||||
<h3 className="text-xs font-semibold text-[var(--color-text-secondary)]">Vault Entry</h3>
|
||||
<p className="m-0 font-mono text-xs text-[var(--color-text-primary)]">#{subject.entryIndex}</p>
|
||||
<DetailNotLoaded />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="grid gap-3">
|
||||
<h3 className="text-xs font-semibold text-[var(--color-text-secondary)]">Vault Entry</h3>
|
||||
<p className="m-0 font-mono text-xs text-[var(--color-text-primary)]">#{subject.entryIndex}</p>
|
||||
{data ? (
|
||||
<MetadataTable
|
||||
rows={[
|
||||
...(data.epistemic_state ? [{ key: "epistemic", value: data.epistemic_state }] : []),
|
||||
...(data.versor_digest
|
||||
? [{ key: "versor", value: data.versor_digest, mono: true, copyable: true }]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<DetailNotLoaded />
|
||||
)}
|
||||
{propositionalForm ? (
|
||||
<p className="m-0 text-sm text-[var(--color-text-primary)] [text-wrap:balance]">
|
||||
{propositionalForm}
|
||||
</p>
|
||||
) : null}
|
||||
<MetadataTable rows={[...coreRows, ...optionalRows, ...handleRows]} />
|
||||
{metadata && Object.keys(metadata).length > 0 ? (
|
||||
<details className="rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface-raised)]">
|
||||
<summary className="cursor-pointer px-3 py-2 text-xs text-[var(--color-text-secondary)]">
|
||||
Raw metadata
|
||||
</summary>
|
||||
<pre className="m-0 max-h-80 overflow-auto border-t border-[var(--color-border-subtle)] px-3 py-2 font-mono text-xs text-[var(--color-text-primary)]">
|
||||
{stableJson(metadata)}
|
||||
</pre>
|
||||
</details>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string, unknown>;
|
||||
versor_digest?: string | null;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue