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
+81
View File
@@ -0,0 +1,81 @@
import Fastify from "fastify";
import { afterEach, expect, test, vi } from "vitest";
import { memoryRoutes } from "../src/routes/memory.js";
import { DEFAULT_SEMANTIC_RUNTIME } from "../src/workspaces/runtime-renderer.js";
import type { ThtRunner } from "../src/tht/tht-runner.js";
import type { PrincipalContext } from "../src/auth/principal.js";
const apps: ReturnType<typeof Fastify>[] = [];
afterEach(async () => { await Promise.all(apps.splice(0).map(app => app.close())); });
const id = "mem-11111111-1111-4111-8111-111111111111";
const input = { family: "domain_clarification", subject: "Order", scope: "Sales" };
function setup(admin = true) {
const app = Fastify(); apps.push(app);
const principal: PrincipalContext = { issuer: "test", subject: "operator", roles: [admin ? "admin" : "user"],
permissions: admin ? ["memory.manage"] : ["session.use"], isAdmin: admin };
app.addHook("preHandler", async request => { request.principal = principal; });
const run = vi.fn(async () => ({ code: 0, stdout: JSON.stringify({ items: [], total: 0 }), stderr: "" }));
const bound = { runWithRuntimeSnapshot: run } as unknown as ThtRunner;
const withPrincipal = vi.fn(() => bound);
memoryRoutes(app, { runner: { withPrincipal }, runtime: DEFAULT_SEMANTIC_RUNTIME,
registry: {
list: async () => [{ id: "sales", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/missing" }],
read: vi.fn().mockResolvedValue({ workspace: { workspace: { language: "it" } } }),
} });
return { app, run, withPrincipal, principal };
}
test("list binds principal and workspace without requiring a DWH runtime", async () => {
const { app, run, withPrincipal, principal } = setup();
const response = await app.inject("/workspaces/sales/memory?q=Order&page=2&column=id");
expect(response.statusCode).toBe(200);
expect(withPrincipal).toHaveBeenCalledWith(principal);
const [args, snapshot] = run.mock.calls[0] as unknown as [string[], string];
expect(args).toEqual(["memory", "admin", "--workspace", "sales"]);
expect(JSON.parse(snapshot)).toMatchObject({ action: "list", request: { q: "Order", page: 2, column: "id" } });
expect(JSON.parse(snapshot).runtime.memoryLanguage).toBe("it");
});
test.each([
["GET", ""], ["POST", ""], ["GET", "/pending"], ["GET", `/${id}`],
["PUT", `/${id}`], ["DELETE", `/${id}`], ["POST", `/${id}/retry`],
] as const)("non-admin cannot %s %s", async (method, suffix) => {
const { app, run } = setup(false);
const response = await app.inject({ method, url: `/workspaces/sales/memory${suffix}`,
...(method === "PUT" || method === "POST" && suffix === "" ? { payload: input } : {}) });
expect(response.statusCode).toBe(403); expect(run).not.toHaveBeenCalled();
});
test("unknown workspace and invalid input do not invoke the harness", async () => {
const { app, run } = setup();
expect((await app.inject("/workspaces/missing/memory")).statusCode).toBe(404);
expect((await app.inject("/workspaces/sales/memory?page_size=101")).statusCode).toBe(400);
expect((await app.inject({ method: "POST", url: "/workspaces/sales/memory",
payload: { ...input, workspace_id: "foreign" } })).statusCode).toBe(400);
expect(run).not.toHaveBeenCalled();
});
test("create preserves saved-but-unindexed outcome, update carries complete related changes", async () => {
const { app, run } = setup();
run.mockResolvedValue({ code: 0, stdout: JSON.stringify({ id, saved: true, indexed: false }), stderr: "" });
const result = await app.inject({ method: "POST", url: "/workspaces/sales/memory", payload: input });
expect(result.statusCode).toBe(201); expect(result.json()).toEqual({ id, saved: true, indexed: false });
await app.inject({ method: "PUT", url: `/workspaces/sales/memory/${id}`, payload: {
...input, links: [{ target_id: id, meaning: "Related" }], dependencies: [{ database: "dwh" }],
} });
const [, snapshot] = run.mock.calls[1] as unknown as [string[], string];
expect(JSON.parse(snapshot)).toMatchObject({ action: "update", request: { id, card: {
links: [{ target_id: id, meaning: "Related" }], dependencies: [{ database: "dwh" }],
} } });
});
test.each([["memory_not_found", 404], ["memory_conflict", 409], ["memory_unavailable", 503]])(
"maps %s without leaking stderr", async (code, status) => {
const { app, run } = setup();
run.mockResolvedValue({ code: 1, stdout: JSON.stringify({ code, message: "sensitive detail" }), stderr: "secret" });
const result = await app.inject("/workspaces/sales/memory");
expect(result.statusCode).toBe(status); expect(result.json().code).toBe(code);
expect(result.body).not.toMatch(/secret|sensitive/);
},
);