fix: harden workspace activation and snapshot retention

This commit is contained in:
2026-08-04 09:25:06 +02:00
parent 3b23cf3714
commit e4fdbed864
14 changed files with 474 additions and 103 deletions
+95
View File
@@ -30,6 +30,7 @@ const defaultWorkspaceRegistry = {
function buildApp(config: Parameters<typeof buildRealApp>[0], deps: Record<string, unknown> = {}) {
return buildRealApp(config, {
workspaceRuntimeSupport: () => true,
...deps,
workspaceRegistry: { ...defaultWorkspaceRegistry, ...(deps.workspaceRegistry as object | undefined) },
} as any);
@@ -324,6 +325,100 @@ test("creates a session from the active immutable workspace revision", async ()
}));
});
test("rejects an SSH-only workspace before persisting or starting a session", async () => {
const sessionNew = vi.fn(async () => ({ id: "must-not-exist" }));
const ensure = vi.fn(async () => ({ ok: true }));
const createFor = vi.fn();
const abort = vi.fn(async () => {});
const markPersisted = vi.fn(async () => {});
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
thtRunner: { sessionNew, searchPack: async () => {} } as any,
readiness: { ensure } as any,
mgr: { get: () => undefined, createFor } as any,
getSettings: () => ({ workspace: "ssh-workspace" }) as any,
workspaceRuntimeSupport: vi.fn(() => false),
workspaceRegistry: {
acquireSessionRevision: vi.fn(async () => ({
workspace: {
workspace: { schema_version: 2, id: "ssh-workspace", name: "SSH", language: "en" },
dwh: {
engine: "postgres", database: "postgres", schema: "public",
supported_transports: ["ssh_tunnel"],
},
semantic_index: {
vector_store: {
engine: "pgvector", database: "postgres", schema: "vectors",
collection: "documents", dimensions: 768, distance: "cosine",
supported_transports: ["ssh_tunnel"],
},
embedding: {
provider: "ollama_compatible", model: "nomic-embed-text", dimensions: 768,
},
},
llm_policy: { allowed: ["zai/glm-5.2"] },
},
revision: {
id: "ssh-workspace", commit: "a".repeat(40), blob: "b".repeat(40),
snapshotPath: `/data/workspace-registry/snapshots/${"a".repeat(40)}/ssh-workspace.yaml`,
state: "operational",
},
abort,
markPersisted,
})),
} as any,
});
const response = await app.inject({ method: "POST", url: "/sessions", payload: { question: "q" } });
expect(response.statusCode).toBe(409);
expect(response.json()).toMatchObject({ code: "workspace_not_activatable" });
expect(ensure).not.toHaveBeenCalled();
expect(sessionNew).not.toHaveBeenCalled();
expect(createFor).not.toHaveBeenCalled();
expect(abort).toHaveBeenCalledOnce();
expect(markPersisted).not.toHaveBeenCalled();
});
test("hands a revision lease to retention only after the session manifest is durable", async () => {
const persisted = deferred<{ id: string }>();
const markPersisted = vi.fn(async () => {});
const abort = vi.fn(async () => {});
const acquireSessionRevision = vi.fn(async () => ({
workspace: { llm_policy: { allowed: ["zai/glm-5.2"] } },
revision: {
id: "leased", commit: "a".repeat(40), blob: "b".repeat(40),
snapshotPath: `/data/workspace-registry/snapshots/${"a".repeat(40)}/leased.yaml`,
state: "operational",
},
markPersisted,
abort,
}));
const app = buildApp(loadConfig({ THT_HARNESS_DIR: "../harness" }), {
thtRunner: { sessionNew: () => persisted.promise, searchPack: async () => {} } as any,
readiness: { ensure: async () => ({ ok: true }) } as any,
mgr: {
get: () => undefined,
createFor: () => ({ bridge: { onClientEvent: () => {} } }),
configure: async () => {},
start: () => {},
} as any,
getSettings: () => ({ workspace: "leased", provider: "zai", model: "glm-5.2" }) as any,
listModels: async () => [{ provider: "zai", id: "glm-5.2", name: "GLM", reasoning: true }],
workspaceRuntimeSupport: () => true,
workspaceRegistry: { acquireSessionRevision } as any,
});
const request = app.inject({ method: "POST", url: "/sessions", payload: { question: "q" } });
await new Promise((resolve) => setImmediate(resolve));
expect(markPersisted).not.toHaveBeenCalled();
expect(abort).not.toHaveBeenCalled();
persisted.resolve({ id: "leased-session" });
expect((await request).statusCode).toBe(200);
expect(markPersisted).toHaveBeenCalledOnce();
expect(abort).not.toHaveBeenCalled();
});
test("creates a session from the configured default workspace revision when workspaceId is omitted", async () => {
const sessionNew = vi.fn(async () => ({ id: "default-pinned" }));
const registry = {