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();
|
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", () => {
|
it("shows a transient Copied confirmation after an address copy", () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useEvidenceSubject, type EvidenceSubject } from "./evidenceContext";
|
import { useEvidenceSubject, type EvidenceSubject } from "./evidenceContext";
|
||||||
import { EvidenceChainRail } from "./EvidenceChainRail";
|
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 { DigestBadge } from "../design/components/DigestBadge/DigestBadge";
|
||||||
import { Timestamp } from "../design/components/Timestamp/Timestamp";
|
import { Timestamp } from "../design/components/Timestamp/Timestamp";
|
||||||
import {
|
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" }> }) {
|
function VaultEntryInspector({ subject }: { subject: Extract<EvidenceSubject, { kind: "vault_entry" }> }) {
|
||||||
const { data } = subject;
|
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 (
|
return (
|
||||||
<div className="grid gap-3">
|
<div className="grid gap-3">
|
||||||
<h3 className="text-xs font-semibold text-[var(--color-text-secondary)]">Vault Entry</h3>
|
<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>
|
<p className="m-0 font-mono text-xs text-[var(--color-text-primary)]">#{subject.entryIndex}</p>
|
||||||
{data ? (
|
{propositionalForm ? (
|
||||||
<MetadataTable
|
<p className="m-0 text-sm text-[var(--color-text-primary)] [text-wrap:balance]">
|
||||||
rows={[
|
{propositionalForm}
|
||||||
...(data.epistemic_state ? [{ key: "epistemic", value: data.epistemic_state }] : []),
|
</p>
|
||||||
...(data.versor_digest
|
) : null}
|
||||||
? [{ key: "versor", value: data.versor_digest, mono: true, copyable: true }]
|
<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
|
||||||
<DetailNotLoaded />
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,13 @@ export interface LogosPackSubjectData
|
||||||
|
|
||||||
export interface VaultEntrySubjectData {
|
export interface VaultEntrySubjectData {
|
||||||
entry_index?: number;
|
entry_index?: number;
|
||||||
|
epistemic_status?: string;
|
||||||
epistemic_state?: 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;
|
versor_digest?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue