feat: implement memory and evidence administration with guided repairs
Publish documentation / publish (push) Successful in 1m27s
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:
@@ -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;
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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)}`);
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user