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
@@ -0,0 +1,86 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { server } from "../test/msw";
import { EvidenceManagementPage } from "./EvidenceManagementPage";
import type { EvidencePage, EvidenceUnit } from "../api/evidence";
const item: EvidenceUnit = { id: "evidence:order-key", title: "Order key", kind: "domain", language: "en", purposes: ["sql_generation"],
applies_to: { concepts: ["Orders"], tables: ["sales.orders"], columns: [] }, payload: { rule: "Join by order number, year and company." },
provenance: { kind: "manual", declared_by: "Curator", original: { source_file: "source/orders.md", supporting_excerpts: ["Old original wording."] } },
review_items: [], file: "curated/domain/order-key.md", status: "modified", revision: "rev" };
const page: EvidencePage = { items: [item], item, total: 1, page: 1, page_size: 25, errors: [], initialized: true, active_revision: "active", pending_revision: null,
location: { repository: "/srv/workspaces/repo", workspace: "/srv/workspaces/repo/sales", runtime_workspace: "/data/registry/repo/sales", host: "Installation host",
command: "tht workspace evidence consolidate --workspace sales", git_commands: ["git status", "git diff", "git add -A", "git commit", "git push"] } };
function renderPage() {
render(<QueryClientProvider client={new QueryClient({ defaultOptions: { queries: { retry: false } } })}><EvidenceManagementPage canManage /></QueryClientProvider>);
}
beforeEach(() => server.use(
http.get("/api/workspaces", () => HttpResponse.json([{ id: "sales", displayName: "Sales" }])),
http.get("/api/workspaces/sales/evidence", () => HttpResponse.json(page)),
http.get("/api/workspaces/sales/evidence/evidence:order-key", () => HttpResponse.json(page)),
));
async function choose() { await screen.findByRole("option", { name: "Sales" }); await userEvent.selectOptions(screen.getByLabelText("Workspace"), "sales"); }
test("reads complete content, manual lineage and actual host file path", async () => {
renderPage(); await choose(); await userEvent.click(await screen.findByRole("button", { name: "Order key" }));
expect(await screen.findByText("Join by order number, year and company.")).toBeVisible();
expect(screen.getByText("Manual declaration by Curator.")).toBeVisible();
expect(screen.getByText(/Original document \(lineage\)/)).toBeVisible();
expect(screen.getByText("/srv/workspaces/repo/sales/evidence/curated/domain/order-key.md")).toBeVisible();
expect(screen.queryByRole("button", { name: "Save" })).not.toBeInTheDocument();
});
test("shows manual consolidation instructions and all kind templates", async () => {
renderPage(); await choose(); await userEvent.click(await screen.findByText("Edit files, consolidate and commit"));
expect(screen.getByText(page.location.command)).toBeVisible();
await userEvent.click(screen.getByText("Create a manual Evidence file"));
await userEvent.selectOptions(screen.getByLabelText("Example kind"), "formula");
expect(screen.getAllByText(/sum\(amount\)/).length).toBeGreaterThan(0);
});
test("combines complete-archive filters and refreshes pending state", async () => {
server.use(http.get("/api/workspaces/sales/evidence", ({ request }) => {
const query = new URL(request.url).searchParams;
return HttpResponse.json({ ...page, items: query.get("kind") === "domain" && query.get("q") === "order" ? [item] : [], pending_revision: "pending" });
}));
renderPage(); await choose(); await userEvent.type(screen.getByLabelText("Search Evidence"), "order");
await userEvent.selectOptions(screen.getByLabelText("Kind"), "domain");
expect(await screen.findByRole("button", { name: "Order key" })).toBeVisible();
expect(screen.getByText(/Consolidation is incomplete/)).toBeVisible();
});
test("invalid files remain actionable alongside valid units", async () => {
server.use(http.get("/api/workspaces/sales/evidence", () => HttpResponse.json({ ...page, errors: [{ file: "curated/domain/broken.md", message: "Missing Rule section" }] })));
renderPage(); await choose();
expect(await screen.findByRole("alert")).toHaveTextContent("curated/domain/broken.md");
expect(within(screen.getByRole("region", { name: "Evidence list" })).getByRole("button", { name: "Order key" })).toBeVisible();
});
test("source refresh is explicit and presents current versus proposed content before a decision", async () => {
const requests: unknown[] = [];
const source = {id: "a".repeat(64), revision: "b".repeat(64), uri: "https://docs.example.test/orders.md", status: "review", availability: "available",
current: [item], proposed: [{...item, payload: {rule: "Use the new approved key."}}], removed_ids: []};
server.use(http.get("/api/workspaces/sales/evidence", () => HttpResponse.json({...page, source_reviews: [source]})),
http.post("/api/workspaces/sales/evidence/sources", async ({request}) => { requests.push(await request.json()); return HttpResponse.json({status: "succeeded", warnings: ["Source decision activated locally."]}); }));
renderPage(); await choose();
await screen.findByText(source.uri);
expect(requests).toHaveLength(0);
await userEvent.click(screen.getByRole("button", {name: "Import or refresh sources"}));
await screen.findByText("Source decision activated locally.");
expect(requests).toEqual([{action: "refresh"}]);
await userEvent.click(screen.getByText(source.uri));
expect(screen.getByText("Current local Evidence")).toBeVisible();
expect(screen.getByText("Use the new approved key.")).toBeVisible();
await userEvent.click(screen.getByRole("button", {name: "Keep local Evidence"}));
expect(requests[1]).toEqual({action: "decide", sourceId: source.id, revision: source.revision, decision: "keep"});
});
test("saved source decisions expose retry and preserve the original choice", async () => {
const source = {id: "a".repeat(64), revision: "b".repeat(64), uri: "s3://docs/orders.md", status: "applying", decision: "replace", availability: "available",
current: [item], proposed: [item], removed_ids: []};
server.use(http.get("/api/workspaces/sales/evidence", () => HttpResponse.json({...page, source_reviews: [source]})),
http.post("/api/workspaces/sales/evidence/sources", () => HttpResponse.json({status: "failed", warnings: ["Source decision saved, but indexing failed. Retry the same decision."]})));
renderPage(); await choose(); await userEvent.click(await screen.findByText(source.uri));
expect(screen.getByRole("button", {name: "Import or refresh sources"})).toBeDisabled();
expect(screen.queryByRole("button", {name: "Keep local Evidence"})).not.toBeInTheDocument();
await userEvent.click(screen.getByRole("button", {name: "Retry saved decision"}));
expect(await screen.findByRole("alert")).toHaveTextContent("Retry the same decision");
});