feat: implement memory and evidence administration with guided repairs
Publish documentation / publish (push) Successful in 1m27s

Add PostgreSQL-backed memory, editable evidence with source review and activation, and human-approved archive repairs across the harness, API, and UI. Include migrations, deployment support, regression coverage, and validation documentation.

Refresh permissions from validated session roles so existing administrator logins can access newly deployed archive management features.
This commit is contained in:
Codex
2026-09-10 10:31:34 +02:00
parent 8fe526dd6e
commit 82e2c91f42
168 changed files with 11914 additions and 1772 deletions
+2 -2
View File
@@ -258,7 +258,7 @@ export type CatalogSyncScope = "tables" | "columns" | "relationships" | "all";
export type CatalogSyncState = "queued" | "running" | "awaiting_confirmation" | "applying"
| "succeeded" | "failed" | "cancelled" | "interrupted";
export type CatalogSyncPhase = "queued" | "connecting" | "scanning_tables" | "scanning_columns"
| "scanning_relationships" | "planning" | "awaiting_confirmation" | "applying" | "completed";
| "scanning_relationships" | "planning" | "awaiting_confirmation" | "applying" | "memory_cleanup" | "completed";
export interface CatalogSchemaDiff {
deletedTables: string[];
@@ -276,7 +276,7 @@ export interface CatalogSyncRun {
requestedDatabaseVersion: number;
plannedDiff: CatalogSchemaDiff | null;
confirmationToken: string | null;
counts: { tables?: number; columns?: number; relationships?: number; created?: number; updated?: number; deleted?: number };
counts: { tables?: number; columns?: number; relationships?: number; created?: number; updated?: number; deleted?: number; memoryDeleted?: number };
errorCode: string | null;
errorMessage: string | null;
cancelRequested: boolean;
+9
View File
@@ -7,6 +7,8 @@ import {
const MAX_ERROR_BODY_BYTES = 8 * 1024;
const safeErrorCodes = new Set([
"evidence_unavailable", "evidence_not_found",
"memory_invalid", "memory_forbidden", "memory_not_found", "memory_conflict", "memory_unavailable",
"auth_forbidden", "auth_invalid_credentials", "auth_not_authorized", "auth_unavailable",
"auth_not_implemented", "authentication_required", "invalid_credentials", "login_rate_limited",
"csrf_failed", "csrf_invalid", "dwh_unreachable", "model_unavailable",
@@ -46,6 +48,13 @@ type SafeErrorPayload = {
};
const localCodeMessages: Record<string, string> = {
evidence_unavailable: "Evidence archive is unavailable. Check the local files and installation configuration.",
evidence_not_found: "Evidence was not found. Refresh the file list.",
memory_invalid: "Check the card fields, family requirements and schema dependencies.",
memory_forbidden: "Memory administration is not permitted.",
memory_not_found: "This Memory card or pending update no longer exists in the workspace.",
memory_conflict: "A linked card is missing or invalid. Review the links and retry.",
memory_unavailable: "The Memory archive is unavailable. Check the installation connection and migrations, then retry.",
auth_forbidden: "Access is not permitted.",
auth_invalid_credentials: "Invalid username or password.",
auth_not_authorized: "Access is not permitted.",
+42
View File
@@ -0,0 +1,42 @@
import { apiFetch } from "./client";
export interface EvidenceUnit {
id: string; title: string; kind: string; language: string; purposes: string[];
applies_to: { concepts: string[]; tables: string[]; columns: string[] };
payload: Record<string, string | string[] | Record<string, string>>;
provenance: { kind?: "manual"; declared_by?: string; source_file?: string; supporting_excerpts?: string[];
original?: { source_file: string; supporting_excerpts: string[] } | null };
review_items: Array<{ code: string; message: string; field?: string }>;
file: string; status: string; revision: string;
}
export interface EvidencePage {
source_reviews?: EvidenceSourceReview[];
items: EvidenceUnit[]; item?: EvidenceUnit; total: number; page: number; page_size: number;
errors: Array<{ file: string; message: string }>; initialized: boolean;
active_revision: string | null; pending_revision: string | null;
location: { repository: string | null; workspace: string | null; runtime_workspace: string;
host: string; command: string; git_commands: string[] };
}
export interface EvidenceSourceReview {
id: string; uri: string; revision: string; availability: "available" | "missing";
status: "review" | "applying" | "accepted" | "kept"; decision?: "keep" | "replace";
current?: EvidenceUnit[]; proposed: EvidenceUnit[]; removed_ids?: string[]; suppressed?: boolean;
}
export type EvidenceSourceAction = { action: "refresh" } | {
action: "decide"; sourceId: string; revision: string; decision: "keep" | "replace";
};
export async function actOnEvidenceSources(workspace: string, action: EvidenceSourceAction) {
const result = await apiFetch<{status: string; warnings?: string[]}>(
`/workspaces/${encodeURIComponent(workspace)}/evidence/sources`, {method: "POST", body: JSON.stringify(action)});
if (result.status !== "succeeded") throw new Error(result.warnings?.join(" ") || "Source operation failed.");
return result;
}
export function listEvidence(workspace: string, filters: Record<string, string | number> = {}) {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(filters)) if (value !== "") params.set(key, String(value));
return apiFetch<EvidencePage>(`/workspaces/${encodeURIComponent(workspace)}/evidence?${params}`);
}
export function getEvidence(workspace: string, id: string) {
return apiFetch<EvidencePage>(`/workspaces/${encodeURIComponent(workspace)}/evidence/${encodeURIComponent(id)}`);
}
+41
View File
@@ -0,0 +1,41 @@
import { apiFetch } from "./client";
export type MemoryFamily = "domain_clarification" | "sql_rule" | "solved_question" | "explained_error";
export interface MemoryDependency { database: string; schema_name: string; table: string; column: string }
export interface MemoryLink { target_id: string; meaning: string }
export interface MemoryInput {
family: MemoryFamily; subject: string; detail: string; scope: string; rationale: string;
question: string; sql: string; concepts: string[]; dependencies: MemoryDependency[]; links: MemoryLink[];
}
export interface MemoryCard extends MemoryInput {
id: string; workspace_id: string; origin: "manual" | "workflow"; session_id: string | null;
decision_seq: number | null; created_at: string; updated_at: string; revision: string; indexed: boolean;
}
export interface MemoryPage { items: MemoryCard[]; total: number; page: number; page_size: number }
export interface MemoryPending { card_id: string; action: "upsert" | "delete"; error: string | null }
export interface MemoryOutcome {
id: string; saved: boolean; indexed: boolean; action: "upsert" | "delete";
card?: MemoryCard; error: string | null;
}
const base = (workspace: string) => `/workspaces/${encodeURIComponent(workspace)}/memory`;
export function listMemory(workspace: string, filters: Record<string, string | number> = {}) {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(filters)) if (value !== "") params.set(key, String(value));
return apiFetch<MemoryPage>(`${base(workspace)}?${params}`);
}
export const getMemory = (workspace: string, id: string) =>
apiFetch<MemoryCard>(`${base(workspace)}/${encodeURIComponent(id)}`);
export const pendingMemory = (workspace: string) => apiFetch<MemoryPending[]>(`${base(workspace)}/pending`);
export const saveMemory = (workspace: string, card: MemoryInput, id?: string) =>
apiFetch<MemoryOutcome>(`${base(workspace)}${id ? `/${encodeURIComponent(id)}` : ""}`, {
method: id ? "PUT" : "POST", body: JSON.stringify(card),
});
export const deleteMemory = (workspace: string, id: string) =>
apiFetch<MemoryOutcome>(`${base(workspace)}/${encodeURIComponent(id)}`, { method: "DELETE" });
export const retryMemory = (workspace: string, id: string) =>
apiFetch<MemoryOutcome>(`${base(workspace)}/${encodeURIComponent(id)}/retry`, { method: "POST" });
export function memoryInput(card: MemoryCard): MemoryInput {
const { family, subject, detail, scope, rationale, question, sql, concepts, dependencies, links } = card;
return { family, subject, detail, scope, rationale, question, sql, concepts, dependencies, links };
}