feat: pin sessions to workspace revisions

This commit is contained in:
2026-08-04 04:52:06 +02:00
parent 2573d87a0e
commit 90894176b6
13 changed files with 313 additions and 80 deletions
+112 -2
View File
@@ -1,4 +1,4 @@
import { test, expect } from "vitest";
import { test, expect, vi } from "vitest";
import { spawn as nodeSpawn } from "node:child_process";
import path from "node:path";
import os from "node:os";
@@ -148,6 +148,44 @@ test("new sessions are created through the authenticated principal, not a client
expect(principal).toMatchObject({ issuer: "portal", subject: "alice" });
});
test("creates a session from the active immutable workspace revision", async () => {
const sessionNew = vi.fn(async () => ({ id: "pinned" }));
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
thtRunner: { sessionNew, searchPack: async () => {} } as any,
readiness: { ensure: async () => ({ ok: true }) } as any,
mgr: {
get: () => undefined,
createFor: () => ({ bridge: { onClientEvent: () => {} } }),
configure: async () => {},
start: () => {},
} as any,
getSettings: () => ({ provider: "zai", model: "glm-5.2", thinking: "low" }) as any,
listModels: async () => [{ provider: "zai", id: "glm-5.2", name: "GLM 5.2", reasoning: true }],
workspaceRegistry: {
read: vi.fn(async () => ({
workspace: {
workspace: { schema_version: 2, id: "psd-clinical", name: "PSD", language: "it" },
dwh: {}, semantic_index: {}, llm_policy: { allowed: ["zai/glm-5.2"] },
},
revision: {
id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40),
snapshotPath: "/data/workspace-registry/snapshots/abc/psd-clinical.yaml", state: "operational",
},
})),
} as any,
});
await app.inject({
method: "POST", url: "/sessions",
payload: { question: "q", workspaceId: "psd-clinical", provider: "zai", model: "glm-5.2", thinking: "low" },
});
expect(sessionNew).toHaveBeenCalledWith(expect.objectContaining({
workspaceConfigPath: "/data/workspace-registry/snapshots/abc/psd-clinical.yaml",
workspaceId: "psd-clinical", workspaceRevision: "a".repeat(40),
}));
});
test("POST /sessions usa i settings (workspace/provider/model/thinking) e crea+avvia", async () => {
const modelKey = path.join(os.tmpdir(), `thoth-model-key-${process.pid}`);
writeFileSync(modelKey, "test-model-key", { mode: 0o600 });
@@ -171,7 +209,7 @@ test("POST /sessions usa i settings (workspace/provider/model/thinking) e crea+a
});
const created = await app.inject({ method: "POST", url: "/sessions", payload: { question: "q" } });
expect(created.json()).toEqual({ id: "s1" });
expect(sessionNewArg.workspace).toBe("w");
expect(sessionNewArg.workspaceConfigPath).toBe("w");
expect(sessionNewArg.provider).toBe("zai");
expect(sessionNewArg.model).toBe("glm-5.2");
expect(sessionNewArg.thinking).toBe("high");
@@ -373,6 +411,78 @@ test("POST /sessions/:id/resume configura Pi con il thinking persistito", async
expect(configured.thinking).toBe("medium");
});
test("POST /sessions/:id/resume uses the manifest's retained workspace revision", async () => {
const reopenSession = vi.fn(async () => {});
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
mgr: {
get: () => undefined,
createFor: () => ({ bridge: { onClientEvent: () => {} } }),
configure: async () => {}, start: () => {},
} as any,
thtRunner: {
sessionShow: async () => ({
status: "open", archived: false, workspace_id: "psd-clinical", workspace_revision: "a".repeat(40),
}),
reopenSession,
} as any,
readiness: { ensure: async () => ({ ok: true }) } as any,
getSettings: () => ({ workspace: "legacy" }) as any,
workspaceRegistry: {
readPinned: vi.fn(async () => ({
workspace: { workspace: { id: "psd-clinical" } },
revision: {
id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40),
snapshotPath: "/data/workspace-registry/snapshots/aaaaaaaa/psd-clinical.yaml", state: "operational",
},
})),
} as any,
});
const response = await app.inject({ method: "POST", url: "/sessions/pinned/resume" });
expect(response.statusCode).toBe(200);
expect(reopenSession).toHaveBeenCalledWith(
"pinned", "/data/workspace-registry/snapshots/aaaaaaaa/psd-clinical.yaml",
);
});
test("POST /sessions/:id/resume returns a sanitized error when its retained revision is unavailable", async () => {
const rawFailure = "cannot read /data/workspace-registry/snapshots/secret-revision";
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
thtRunner: {
sessionShow: async () => ({
status: "open", archived: false, workspace_id: "psd-clinical", workspace_revision: "a".repeat(40),
}),
} as any,
getSettings: () => ({ workspace: "legacy" }) as any,
workspaceRegistry: { readPinned: async () => { throw new Error(rawFailure); } } as any,
});
const response = await app.inject({ method: "POST", url: "/sessions/pinned/resume" });
expect(response.statusCode).toBe(409);
expect(response.body).not.toContain(rawFailure);
expect(response.json()).toMatchObject({ code: "workspace_revision_unavailable" });
});
test("GET /sessions/:id warns when a legacy manifest has no workspace revision", async () => {
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
mgr: {
get: () => undefined,
createFor: () => ({ bridge: { onClientEvent: () => {} } }),
configure: async () => {}, start: () => {},
} as any,
thtRunner: { sessionShow: async () => ({ status: "open", archived: false }), reopenSession: async () => {} } as any,
readiness: { ensure: async () => ({ ok: true }) } as any,
getSettings: () => ({ workspace: "legacy" }) as any,
});
const response = await app.inject({ method: "GET", url: "/sessions/legacy" });
expect(response.statusCode).toBe(200);
expect(response.json()).toMatchObject({ warning: expect.stringMatching(/legacy/i) });
});
test("POST /sessions/:id/resume usa il thinking globale se manca nel manifest", async () => {
let configured: any;
const bridge = { onClientEvent: () => {}, emitClientEvent: () => {} };