refactor: make workspace descriptors schema v3 only
This commit is contained in:
@@ -15,8 +15,8 @@ import { createProductionWorkspaceDiagnoser } from "../src/workspaces/diagnostic
|
||||
import { WorkspaceRegistryError } from "../src/workspaces/git-repository.js";
|
||||
import { WorkspaceRegistry, type WorkspaceRevision } from "../src/workspaces/registry.js";
|
||||
import {
|
||||
parseWorkspaceYaml, renderWorkspaceDocs, serializeWorkspaceYaml, validateCanonicalWorkspace,
|
||||
type CanonicalWorkspace, type WorkspaceV2,
|
||||
parseWorkspaceYaml, renderWorkspaceDocs, serializeWorkspaceYaml, validateWorkspaceDescriptor,
|
||||
type CanonicalWorkspace,
|
||||
} from "../src/workspaces/schema.js";
|
||||
|
||||
const workspace: CanonicalWorkspace = {
|
||||
@@ -49,7 +49,7 @@ const workspace: CanonicalWorkspace = {
|
||||
llm_policy: { allowed: ["zai/glm-5.2"] },
|
||||
};
|
||||
|
||||
const workspaceV2: WorkspaceV2 = {
|
||||
const workspaceV2 = {
|
||||
workspace: {
|
||||
schema_version: 2,
|
||||
id: "psd-clinical",
|
||||
@@ -104,6 +104,11 @@ const workspaceV2: WorkspaceV2 = {
|
||||
llm_policy: { allowed: ["zai/glm-5.2"] },
|
||||
};
|
||||
|
||||
const workspaceV1 = {
|
||||
...workspaceV2,
|
||||
workspace: { ...workspaceV2.workspace, schema_version: 1 as const },
|
||||
};
|
||||
|
||||
const revision: WorkspaceRevision = {
|
||||
id: workspace.workspace.id,
|
||||
commit: "a".repeat(40),
|
||||
@@ -248,6 +253,31 @@ test("validates a canonical workspace and runs the injected installation diagnos
|
||||
expect(diagnose).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test.each([
|
||||
["v1", workspaceV1],
|
||||
["v2", workspaceV2],
|
||||
])("rejects schema %s at validate and publish boundaries with a sanitized error", async (_version, legacy) => {
|
||||
const registry = registryFake();
|
||||
const app = appFor(registry);
|
||||
|
||||
for (const request of [
|
||||
{ url: "/workspaces/validate", payload: { workspace: legacy } },
|
||||
{
|
||||
url: "/workspaces/publish",
|
||||
payload: { action: "create", workspace: legacy, baseCommit: revision.commit },
|
||||
},
|
||||
]) {
|
||||
const response = await app.inject({ method: "POST", ...request });
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.json()).toEqual({
|
||||
code: "workspace_invalid",
|
||||
message: "Workspace request or bundle is invalid.",
|
||||
});
|
||||
expect(response.body).not.toMatch(/migration_required|schema version/i);
|
||||
}
|
||||
expect(registry.publish).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("rejects a migration-required v2 workspace before resolving semantic diagnostics", async () => {
|
||||
const diagnose = vi.fn(async () => ({
|
||||
activatable: false,
|
||||
@@ -342,12 +372,12 @@ test("returns a 409 field conflict instead of overwriting a changed workspace",
|
||||
const conflict = Object.assign(
|
||||
new WorkspaceRegistryError("workspace_conflict", "Workspace has changed"),
|
||||
{
|
||||
fields: ["semantic_index.embedding.model"],
|
||||
fields: ["workspace.description"],
|
||||
expected: { commit: "c".repeat(40), blob: "d".repeat(40) },
|
||||
actual: { commit: revision.commit, blob: revision.blob },
|
||||
base: workspace,
|
||||
local: { ...workspace, semantic_index: { ...workspace.semantic_index, embedding: { ...workspace.semantic_index.embedding, model: "local/model" } } },
|
||||
remote: { ...workspace, semantic_index: { ...workspace.semantic_index, embedding: { ...workspace.semantic_index.embedding, model: "remote/model" } } },
|
||||
local: { ...workspace, workspace: { ...workspace.workspace, description: "Local description" } },
|
||||
remote: { ...workspace, workspace: { ...workspace.workspace, description: "Remote description" } },
|
||||
},
|
||||
);
|
||||
const registry = registryFake({ publish: vi.fn(async () => { throw conflict; }) });
|
||||
@@ -364,14 +394,12 @@ test("returns a 409 field conflict instead of overwriting a changed workspace",
|
||||
expect(res.statusCode).toBe(409);
|
||||
expect(res.json()).toMatchObject({
|
||||
code: "workspace_conflict",
|
||||
fields: ["semantic_index.embedding.model"],
|
||||
fields: ["workspace.description"],
|
||||
expected: { commit: "c".repeat(40), blob: "d".repeat(40) },
|
||||
actual: { commit: revision.commit, blob: revision.blob },
|
||||
base: workspace,
|
||||
remote: expect.objectContaining({
|
||||
semantic_index: expect.objectContaining({
|
||||
embedding: expect.objectContaining({ model: "remote/model" }),
|
||||
}),
|
||||
workspace: expect.objectContaining({ description: "Remote description" }),
|
||||
}),
|
||||
});
|
||||
});
|
||||
@@ -450,7 +478,7 @@ function withEvidence(
|
||||
source: Partial<CanonicalWorkspace["evidence"]["source"]> & { type: "filesystem" | "http" | "s3" },
|
||||
changes: Partial<CanonicalWorkspace["evidence"]["policy"]> = {},
|
||||
): CanonicalWorkspace {
|
||||
return validateCanonicalWorkspace({
|
||||
return validateWorkspaceDescriptor({
|
||||
...workspace,
|
||||
evidence: { source, policy: changes },
|
||||
});
|
||||
@@ -587,7 +615,7 @@ test.each([
|
||||
test("real publish create/update, pull, list, and read preserve a complete Evidence descriptor", async () => {
|
||||
const fixture = await createRealRouteFixture(httpEvidenceWorkspace);
|
||||
const status = await fixture.app.inject({ method: "GET", url: "/workspace-registry/status" });
|
||||
const created = validateCanonicalWorkspace({
|
||||
const created = validateWorkspaceDescriptor({
|
||||
...httpEvidenceWorkspace,
|
||||
workspace: { ...httpEvidenceWorkspace.workspace, id: "research-clinical", name: "Research Clinical" },
|
||||
semantic_index: {
|
||||
@@ -600,7 +628,7 @@ test("real publish create/update, pull, list, and read preserve a complete Evide
|
||||
payload: { action: "create", workspace: created, baseCommit: status.json().head },
|
||||
});
|
||||
const createdRevision = create.json().revision as WorkspaceRevision;
|
||||
const updated = validateCanonicalWorkspace({
|
||||
const updated = validateWorkspaceDescriptor({
|
||||
...created,
|
||||
evidence: {
|
||||
...created.evidence,
|
||||
@@ -617,7 +645,7 @@ test("real publish create/update, pull, list, and read preserve a complete Evide
|
||||
});
|
||||
expect(update.statusCode).toBe(200);
|
||||
await realGit(fixture.author, ["pull", "--ff-only", "origin", "main"]);
|
||||
const remotelyEdited = validateCanonicalWorkspace({
|
||||
const remotelyEdited = validateWorkspaceDescriptor({
|
||||
...updated,
|
||||
evidence: {
|
||||
...updated.evidence,
|
||||
@@ -742,7 +770,7 @@ test("real publish and pull fail safely when the contextual Evidence Git tree is
|
||||
const fixture = await createRealRouteFixture();
|
||||
await fixture.registry.bootstrap();
|
||||
const current = await fixture.registry.read("psd-clinical");
|
||||
const missing = validateCanonicalWorkspace({
|
||||
const missing = validateWorkspaceDescriptor({
|
||||
...workspace,
|
||||
workspace: { ...workspace.workspace, id: "missing-evidence", name: "Missing Evidence" },
|
||||
semantic_index: {
|
||||
|
||||
Reference in New Issue
Block a user