Close the deferred Vault item 4: a read-only endpoint proving a selected
vault entry is recallable by CORE's actual exact CGA machinery, surfaced
in the entry inspector.
Backend (read-only, cold-persisted only):
- GET /vault/entries/{index}/recall rehydrates the persisted VaultStore
(VaultStore.from_dict — bit-exact versors, no reprojection) and runs the
real VaultStore.recall using the entry's own stored versor as the query.
Exact cga_inner scan — never ANN / cosine / approximate.
- recall's +inf exact-self-match sentinel never crosses the boundary: the
genuine finite cga_inner is reported plus an exact_self_match flag. The
raw versor never leaves the engine — only content-addressed digests.
- Trust boundary: caller-controlled index -> 404 (out of range / non-int);
absent persisted snapshot -> 501. The file is never written; the live
runtime is never touched; recall is deterministic over persisted bytes.
Frontend:
- "Exact CGA Recall" inspector panel, collapsed by default so the read is
opt-in (the query hook only mounts on expand). Copy says "exact CGA
recall" / cga_inner — never similarity / relevance / score / ANN /
cosine. Honestly surfaces that a byte-identical self-match is promoted
ahead of metric ranking (CGA null-vector self inner-product ~0).
INV-24: register workbench/readers.py in VAULT_RECALL_SITES as
EVIDENCE_TELEMETRY (operator inspection evidence, not claim-shaping), and
tighten the recall-site detector to recognise VaultStore.from_dict factory
bindings so the obligation is real rather than silently bypassed.
Tests: backend 501/404/self-recall/determinism/no-mutation/JSON-safety +
API status codes; frontend collapsed-doctrine + expanded self-recall
evidence. Full workbench suite (192) + architectural invariants (61) +
vault-touching vitest (70) all green.
380 lines
12 KiB
TypeScript
380 lines
12 KiB
TypeScript
import type {
|
|
ApiResponse,
|
|
ErrorCode,
|
|
DemoRunResult,
|
|
DemoSummary,
|
|
EvalLaneSummary,
|
|
EvalRunRequest,
|
|
EvalRunResult,
|
|
ArtifactRef,
|
|
ArtifactDetail,
|
|
TurnReplayComparison,
|
|
ProposalDetail,
|
|
ProposalState,
|
|
ProposalSummary,
|
|
AuditEvent,
|
|
RunSummary,
|
|
RunDetail,
|
|
PackSummary,
|
|
PackDetail,
|
|
LogosPackSummary,
|
|
LogosPackOverview,
|
|
LogosSafetyReport,
|
|
LogosPackContents,
|
|
LogosAlignmentRow,
|
|
VaultSummary,
|
|
VaultEntry,
|
|
VaultRecall,
|
|
CalibrationClass,
|
|
ServingMetrics,
|
|
CognitivePipelineRecord,
|
|
FieldEvidence,
|
|
EvidenceBundle,
|
|
DeterminismTour,
|
|
LivedLife,
|
|
TurnJournalEntry,
|
|
TurnJournalSummary,
|
|
ContemplationRunDetail,
|
|
ContemplationRunSummary,
|
|
MathProposalSummary,
|
|
MathProposalDetail,
|
|
MathRatifyResult,
|
|
HealthStatus,
|
|
} from "../types/api";
|
|
|
|
export class WorkbenchApiError extends Error {
|
|
constructor(
|
|
public readonly code: ErrorCode,
|
|
message: string,
|
|
) {
|
|
super(message);
|
|
this.name = "WorkbenchApiError";
|
|
}
|
|
}
|
|
|
|
export const API_URL: string =
|
|
import.meta.env.VITE_WORKBENCH_API_URL ?? "http://127.0.0.1:8765";
|
|
|
|
export async function apiFetch<T>(
|
|
path: string,
|
|
init?: RequestInit & { timeoutMs?: number },
|
|
): Promise<T> {
|
|
const { timeoutMs = 5000, ...requestInit } = init ?? {};
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
try {
|
|
const res = await fetch(`${API_URL}${path}`, { ...requestInit, signal: controller.signal });
|
|
const json: ApiResponse<T> = await res.json();
|
|
if (!json.ok) {
|
|
throw new WorkbenchApiError(json.error.code, json.error.message);
|
|
}
|
|
return json.data;
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|
|
|
|
export async function fetchHealth(): Promise<HealthStatus> {
|
|
// Cheap liveness probe — keep the timeout short so an unresponsive server
|
|
// surfaces as "Unhealthy" quickly rather than blocking the footer poll.
|
|
return apiFetch<HealthStatus>("/health", { timeoutMs: 3000 });
|
|
}
|
|
|
|
export async function fetchEvalLanes(): Promise<EvalLaneSummary[]> {
|
|
const envelope = await apiFetch<{ lanes: EvalLaneSummary[] }>("/evals");
|
|
return envelope.lanes;
|
|
}
|
|
|
|
export async function runEvalLane(req: EvalRunRequest): Promise<EvalRunResult> {
|
|
if (req.split === "holdout") {
|
|
const hasConfig = typeof window !== "undefined" && (window as any).sealedEvalConfig === true;
|
|
if (!hasConfig) {
|
|
throw new WorkbenchApiError(
|
|
"client_refused_sealed_holdout",
|
|
"Holdout runs require sealed-eval config — use CLI"
|
|
);
|
|
}
|
|
}
|
|
|
|
const lanes = await fetchEvalLanes();
|
|
const lane = lanes.find((l) => l.lane === req.lane);
|
|
if (!lane) {
|
|
throw new WorkbenchApiError("not_found", `Eval lane not found: ${req.lane}`);
|
|
}
|
|
if (!lane.read_only) {
|
|
throw new WorkbenchApiError("client_refused_unsafe_lane", "API run disabled — use CLI");
|
|
}
|
|
|
|
return apiFetch<EvalRunResult>("/evals/run", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(req),
|
|
timeoutMs: 120000,
|
|
});
|
|
}
|
|
|
|
export async function fetchArtifacts(): Promise<ArtifactRef[]> {
|
|
const data = await apiFetch<{ items: ArtifactRef[] }>("/artifacts");
|
|
return data.items;
|
|
}
|
|
|
|
export async function fetchArtifactDetail(artifactId: string): Promise<ArtifactDetail> {
|
|
return apiFetch<ArtifactDetail>(`/artifacts/${artifactId}`);
|
|
}
|
|
|
|
export async function fetchTurnReplay(turnId: number): Promise<TurnReplayComparison> {
|
|
return apiFetch<TurnReplayComparison>(`/replay/${encodeURIComponent(String(turnId))}`);
|
|
}
|
|
|
|
export async function fetchDemos(): Promise<DemoSummary[]> {
|
|
const envelope = await apiFetch<ItemsEnvelope<DemoSummary>>("/demos");
|
|
return envelope.items;
|
|
}
|
|
|
|
export async function runDemo(demoId: string): Promise<DemoRunResult> {
|
|
return apiFetch<DemoRunResult>(`/demos/${encodeURIComponent(demoId)}/run`, {
|
|
method: "POST",
|
|
});
|
|
}
|
|
|
|
export async function fetchContemplationRuns(
|
|
limit?: number,
|
|
offset?: number,
|
|
): Promise<ContemplationRunSummary[]> {
|
|
const params = new URLSearchParams();
|
|
if (limit !== undefined) params.set("limit", String(limit));
|
|
if (offset !== undefined) params.set("offset", String(offset));
|
|
const query = params.toString();
|
|
const envelope = await apiFetch<ItemsEnvelope<ContemplationRunSummary>>(
|
|
query ? `/contemplation/runs?${query}` : "/contemplation/runs",
|
|
);
|
|
return envelope.items;
|
|
}
|
|
|
|
export async function fetchContemplationRun(
|
|
runId: string,
|
|
): Promise<ContemplationRunDetail> {
|
|
return apiFetch<ContemplationRunDetail>(
|
|
`/contemplation/runs/${encodeURIComponent(runId)}`,
|
|
);
|
|
}
|
|
|
|
export type ProposalStateFilter = ProposalState | "all";
|
|
|
|
interface ItemsEnvelope<T> {
|
|
items: T[];
|
|
}
|
|
|
|
export async function fetchProposals(
|
|
filter: ProposalStateFilter = "all",
|
|
): Promise<ProposalSummary[]> {
|
|
const envelope = await apiFetch<ItemsEnvelope<ProposalSummary>>("/proposals");
|
|
if (filter === "all") {
|
|
return envelope.items;
|
|
}
|
|
return envelope.items.filter((proposal) => proposal.state === filter);
|
|
}
|
|
|
|
export async function fetchProposalDetail(proposalId: string): Promise<ProposalDetail> {
|
|
return apiFetch<ProposalDetail>(`/proposals/${encodeURIComponent(proposalId)}`);
|
|
}
|
|
|
|
export async function fetchTraceTurns(
|
|
limit?: number,
|
|
offset?: number,
|
|
): Promise<TurnJournalSummary[]> {
|
|
const params = new URLSearchParams();
|
|
if (limit !== undefined) params.set("limit", String(limit));
|
|
if (offset !== undefined) params.set("offset", String(offset));
|
|
const query = params.toString();
|
|
const envelope = await apiFetch<ItemsEnvelope<TurnJournalSummary>>(
|
|
query ? `/trace/turns?${query}` : "/trace/turns",
|
|
);
|
|
return envelope.items;
|
|
}
|
|
|
|
export async function fetchTraceTurn(turnId: number): Promise<TurnJournalEntry> {
|
|
return apiFetch<TurnJournalEntry>(`/trace/${encodeURIComponent(String(turnId))}`);
|
|
}
|
|
|
|
export async function fetchTracePipeline(turnId: number): Promise<CognitivePipelineRecord> {
|
|
return apiFetch<CognitivePipelineRecord>(
|
|
`/trace/${encodeURIComponent(String(turnId))}/pipeline`,
|
|
);
|
|
}
|
|
|
|
export async function fetchTraceField(turnId: number): Promise<FieldEvidence> {
|
|
return apiFetch<FieldEvidence>(
|
|
`/trace/${encodeURIComponent(String(turnId))}/field`,
|
|
);
|
|
}
|
|
|
|
export async function fetchTraceBundle(turnId: number): Promise<EvidenceBundle> {
|
|
return apiFetch<EvidenceBundle>(
|
|
`/trace/${encodeURIComponent(String(turnId))}/bundle`,
|
|
);
|
|
}
|
|
|
|
export async function fetchTour(): Promise<DeterminismTour> {
|
|
return apiFetch<DeterminismTour>("/tour");
|
|
}
|
|
|
|
export async function fetchLivedLife(): Promise<LivedLife> {
|
|
return apiFetch<LivedLife>("/lived-life");
|
|
}
|
|
|
|
export async function fetchAuditEvents(
|
|
limit?: number,
|
|
offset?: number,
|
|
): Promise<ItemsEnvelope<AuditEvent>> {
|
|
const params = new URLSearchParams();
|
|
if (limit !== undefined) params.set("limit", String(limit));
|
|
if (offset !== undefined) params.set("offset", String(offset));
|
|
const query = params.toString();
|
|
return apiFetch<ItemsEnvelope<AuditEvent>>(query ? `/audit/events?${query}` : "/audit/events");
|
|
}
|
|
|
|
export async function fetchRuns(limit?: number, offset?: number): Promise<RunSummary[]> {
|
|
const params = new URLSearchParams();
|
|
if (limit !== undefined) params.set("limit", String(limit));
|
|
if (offset !== undefined) params.set("offset", String(offset));
|
|
const query = params.toString();
|
|
const envelope = await apiFetch<ItemsEnvelope<RunSummary>>(query ? `/runs?${query}` : "/runs");
|
|
return envelope.items;
|
|
}
|
|
|
|
export async function fetchRun(
|
|
sessionId: string,
|
|
turnLimit?: number,
|
|
turnOffset?: number,
|
|
): Promise<RunDetail> {
|
|
const params = new URLSearchParams();
|
|
if (turnLimit !== undefined) params.set("limit", String(turnLimit));
|
|
if (turnOffset !== undefined) params.set("offset", String(turnOffset));
|
|
const query = params.toString();
|
|
const path = `/runs/${encodeURIComponent(sessionId)}`;
|
|
return apiFetch<RunDetail>(query ? `${path}?${query}` : path);
|
|
}
|
|
|
|
export async function fetchMathProposals(): Promise<MathProposalSummary[]> {
|
|
const envelope = await apiFetch<ItemsEnvelope<MathProposalSummary>>("/math-proposals");
|
|
return envelope.items;
|
|
}
|
|
|
|
export async function fetchMathProposalDetail(proposalId: string): Promise<MathProposalDetail> {
|
|
return apiFetch<MathProposalDetail>(`/math-proposals/${encodeURIComponent(proposalId)}`);
|
|
}
|
|
|
|
export async function ratifyMathProposal(
|
|
proposalId: string,
|
|
category?: string,
|
|
polarity?: string,
|
|
dryRun?: boolean,
|
|
): Promise<MathRatifyResult> {
|
|
return apiFetch<MathRatifyResult>(`/math-proposals/${encodeURIComponent(proposalId)}/ratify`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ category, polarity, dry_run: dryRun }),
|
|
});
|
|
}
|
|
|
|
export async function rejectMathProposal(
|
|
proposalId: string,
|
|
note?: string,
|
|
): Promise<{ proposal_id: string; rejected: boolean }> {
|
|
return apiFetch<{ proposal_id: string; rejected: boolean }>(
|
|
`/math-proposals/${encodeURIComponent(proposalId)}/reject`,
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ note }),
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function deferMathProposal(
|
|
proposalId: string,
|
|
): Promise<{ proposal_id: string; deferred: boolean }> {
|
|
return apiFetch<{ proposal_id: string; deferred: boolean }>(
|
|
`/math-proposals/${encodeURIComponent(proposalId)}/defer`,
|
|
{
|
|
method: "POST",
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function fetchPacks(limit?: number, offset?: number): Promise<PackSummary[]> {
|
|
const params = new URLSearchParams();
|
|
if (limit !== undefined) params.set("limit", String(limit));
|
|
if (offset !== undefined) params.set("offset", String(offset));
|
|
const query = params.toString();
|
|
const envelope = await apiFetch<ItemsEnvelope<PackSummary>>(query ? `/packs?${query}` : "/packs");
|
|
return envelope.items;
|
|
}
|
|
|
|
export async function fetchPack(packId: string): Promise<PackDetail> {
|
|
return apiFetch<PackDetail>(`/packs/${encodeURIComponent(packId)}`);
|
|
}
|
|
|
|
export async function fetchLogosPacks(): Promise<LogosPackSummary[]> {
|
|
const envelope = await apiFetch<ItemsEnvelope<LogosPackSummary>>("/logos/packs");
|
|
return envelope.items;
|
|
}
|
|
|
|
export async function fetchLogosPackOverview(packId: string): Promise<LogosPackOverview> {
|
|
return apiFetch<LogosPackOverview>(`/logos/packs/${encodeURIComponent(packId)}`);
|
|
}
|
|
|
|
export async function fetchLogosPackSafety(packId: string): Promise<LogosSafetyReport> {
|
|
return apiFetch<LogosSafetyReport>(
|
|
`/logos/packs/${encodeURIComponent(packId)}/safety`,
|
|
);
|
|
}
|
|
|
|
export async function fetchLogosPackContents(
|
|
packId: string,
|
|
): Promise<LogosPackContents> {
|
|
return apiFetch<LogosPackContents>(
|
|
`/logos/packs/${encodeURIComponent(packId)}/contents`,
|
|
);
|
|
}
|
|
|
|
export async function fetchLogosPackAlignment(
|
|
packId: string,
|
|
): Promise<LogosAlignmentRow[]> {
|
|
const envelope = await apiFetch<ItemsEnvelope<LogosAlignmentRow>>(
|
|
`/logos/packs/${encodeURIComponent(packId)}/alignment`,
|
|
);
|
|
return envelope.items;
|
|
}
|
|
|
|
export async function fetchVaultSummary(): Promise<VaultSummary> {
|
|
return apiFetch<VaultSummary>("/vault/summary");
|
|
}
|
|
|
|
export async function fetchVaultEntries(limit?: number, offset?: number): Promise<VaultEntry[]> {
|
|
const params = new URLSearchParams();
|
|
if (limit !== undefined) params.set("limit", String(limit));
|
|
if (offset !== undefined) params.set("offset", String(offset));
|
|
const query = params.toString();
|
|
const envelope = await apiFetch<ItemsEnvelope<VaultEntry>>(
|
|
query ? `/vault/entries?${query}` : "/vault/entries",
|
|
);
|
|
return envelope.items;
|
|
}
|
|
|
|
export async function fetchVaultEntryRecall(entryIndex: number): Promise<VaultRecall> {
|
|
return apiFetch<VaultRecall>(
|
|
`/vault/entries/${encodeURIComponent(String(entryIndex))}/recall`,
|
|
);
|
|
}
|
|
|
|
export async function fetchCalibrationClasses(): Promise<CalibrationClass[]> {
|
|
const envelope = await apiFetch<ItemsEnvelope<CalibrationClass>>("/calibration/classes");
|
|
return envelope.items;
|
|
}
|
|
|
|
export async function fetchServingMetrics(): Promise<ServingMetrics[]> {
|
|
const envelope = await apiFetch<ItemsEnvelope<ServingMetrics>>("/serving/metrics");
|
|
return envelope.items;
|
|
}
|