refactor: remove workspace revision state
This commit is contained in:
@@ -38,13 +38,13 @@ function operationalWorkspace(id = "default") {
|
||||
const defaultWorkspaceRegistry = {
|
||||
list: vi.fn(async () => [{
|
||||
id: "default", commit: "e".repeat(40), blob: "f".repeat(40),
|
||||
snapshotPath: `/data/workspace-registry/snapshots/${"e".repeat(40)}/default.yaml`, state: "operational",
|
||||
snapshotPath: `/data/workspace-registry/snapshots/${"e".repeat(40)}/default.yaml`,
|
||||
}]),
|
||||
read: vi.fn(async (id: string) => ({
|
||||
workspace: operationalWorkspace(id),
|
||||
revision: {
|
||||
id, commit: "e".repeat(40), blob: "f".repeat(40),
|
||||
snapshotPath: `/data/workspace-registry/snapshots/${"e".repeat(40)}/${id}.yaml`, state: "operational",
|
||||
snapshotPath: `/data/workspace-registry/snapshots/${"e".repeat(40)}/${id}.yaml`,
|
||||
},
|
||||
})),
|
||||
};
|
||||
@@ -306,8 +306,8 @@ test("retention scans a removed workspace's retained snapshot", async () => {
|
||||
const activeSnapshot = "/registry/snapshots/a/other.yaml";
|
||||
const removedSnapshot = "/registry/snapshots/e/removed.yaml";
|
||||
const listRetainedSnapshots = vi.fn(async () => [
|
||||
{ id: "other", commit: "a".repeat(40), state: "operational", snapshotPath: activeSnapshot },
|
||||
{ id: "removed", commit: removedRevision, state: "operational", snapshotPath: removedSnapshot },
|
||||
{ id: "other", commit: "a".repeat(40), snapshotPath: activeSnapshot },
|
||||
{ id: "removed", commit: removedRevision, snapshotPath: removedSnapshot },
|
||||
]);
|
||||
const app = buildApp(loadConfig({ AUTH_MODE: "upstream", THT_HARNESS_DIR: "../harness" }), {
|
||||
thtRunner: {
|
||||
@@ -318,7 +318,7 @@ test("retention scans a removed workspace's retained snapshot", async () => {
|
||||
}),
|
||||
} as any,
|
||||
workspaceRegistry: {
|
||||
list: async () => [{ id: "other", commit: "a".repeat(40), state: "operational", snapshotPath: activeSnapshot }],
|
||||
list: async () => [{ id: "other", commit: "a".repeat(40), snapshotPath: activeSnapshot }],
|
||||
listRetainedSnapshots,
|
||||
reconcileSnapshotRetention: retained,
|
||||
} as any,
|
||||
@@ -472,7 +472,7 @@ test("creates a session from the active immutable workspace revision", async ()
|
||||
},
|
||||
revision: {
|
||||
id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40),
|
||||
snapshotPath: "/data/workspace-registry/snapshots/abc/psd-clinical.yaml", state: "operational",
|
||||
snapshotPath: "/data/workspace-registry/snapshots/abc/psd-clinical.yaml",
|
||||
},
|
||||
})),
|
||||
} as any,
|
||||
@@ -524,7 +524,6 @@ test("rejects an SSH-only workspace before persisting or starting a session", as
|
||||
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,
|
||||
@@ -552,7 +551,6 @@ test("hands a revision lease to retention only after the session manifest is dur
|
||||
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,
|
||||
@@ -590,7 +588,7 @@ test("creates a session from the configured default workspace revision when work
|
||||
workspace: { llm_policy: { allowed: ["zai/glm-5.2"] } },
|
||||
revision: {
|
||||
id, commit: "c".repeat(40), blob: "d".repeat(40),
|
||||
snapshotPath: `/data/workspace-registry/snapshots/${"c".repeat(40)}/${id}.yaml`, state: "operational",
|
||||
snapshotPath: `/data/workspace-registry/snapshots/${"c".repeat(40)}/${id}.yaml`,
|
||||
},
|
||||
})),
|
||||
};
|
||||
@@ -676,11 +674,11 @@ test("session lifecycle locates a B session when installation default is A", asy
|
||||
workspaceRegistry: {
|
||||
read: async (id: string) => ({
|
||||
workspace: { llm_policy: { allowed: ["zai/glm-5.2"] } },
|
||||
revision: { id, commit: "b".repeat(40), blob: "d".repeat(40), snapshotPath: bPath, state: "operational" },
|
||||
revision: { id, commit: "b".repeat(40), blob: "d".repeat(40), snapshotPath: bPath },
|
||||
}),
|
||||
list: async () => [
|
||||
{ id: "a-workspace", commit: "a".repeat(40), blob: "a".repeat(40), snapshotPath: aPath, state: "operational" },
|
||||
{ id: "b-workspace", commit: "b".repeat(40), blob: "b".repeat(40), snapshotPath: bPath, state: "operational" },
|
||||
{ id: "a-workspace", commit: "a".repeat(40), blob: "a".repeat(40), snapshotPath: aPath },
|
||||
{ id: "b-workspace", commit: "b".repeat(40), blob: "b".repeat(40), snapshotPath: bPath },
|
||||
],
|
||||
readPinned: vi.fn(async (id: string, revision: string) => {
|
||||
expect([id, revision]).toEqual(["b-workspace", "c".repeat(40)]);
|
||||
@@ -956,7 +954,7 @@ test("POST /sessions/:id/resume uses the manifest's retained workspace revision"
|
||||
workspace: operationalWorkspace("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",
|
||||
snapshotPath: "/data/workspace-registry/snapshots/aaaaaaaa/psd-clinical.yaml",
|
||||
},
|
||||
})),
|
||||
} as any,
|
||||
@@ -1072,7 +1070,7 @@ test("a pruned pin blocks Resume but not active or mutation lifecycle routes", a
|
||||
} as any,
|
||||
workspaceRegistry: {
|
||||
list: async () => [{
|
||||
id: "b-workspace", commit: "a".repeat(40), blob: "a".repeat(40), snapshotPath: activePath, state: "operational",
|
||||
id: "b-workspace", commit: "a".repeat(40), blob: "a".repeat(40), snapshotPath: activePath,
|
||||
}],
|
||||
readPinned,
|
||||
} as any,
|
||||
@@ -2299,7 +2297,7 @@ test("rename authorizes and mutates through the same registry snapshot", async (
|
||||
} as any,
|
||||
workspaceRegistry: {
|
||||
list: async () => [{
|
||||
id: "tenant-a", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: tenantPath, state: "operational",
|
||||
id: "tenant-a", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: tenantPath,
|
||||
}],
|
||||
} as any,
|
||||
});
|
||||
@@ -2861,7 +2859,6 @@ test.each([
|
||||
const revision = {
|
||||
id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40),
|
||||
snapshotPath: `/data/workspace-registry/snapshots/${"a".repeat(40)}/psd-clinical.yaml`,
|
||||
state: "operational" as const,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -108,7 +108,7 @@ test("registry-backed SQL preview resolves and uses the session's pinned runtime
|
||||
workspaceRegistry: {
|
||||
list: async () => [{
|
||||
id: "psd-clinical", commit: "a".repeat(40), blob: "c".repeat(40),
|
||||
snapshotPath: activePath, state: "operational",
|
||||
snapshotPath: activePath,
|
||||
}],
|
||||
readPinned: async () => ({ workspace: {}, workspaceConfigPath: pinnedPath }),
|
||||
} as any,
|
||||
|
||||
@@ -114,7 +114,6 @@ const revision: WorkspaceRevision = {
|
||||
commit: "a".repeat(40),
|
||||
blob: "b".repeat(40),
|
||||
snapshotPath: "/registry/snapshots/psd-clinical.yaml",
|
||||
state: "operational",
|
||||
};
|
||||
|
||||
type RegistryFake = Pick<WorkspaceRegistry, "bootstrap" | "pull" | "list" | "read" | "publish">;
|
||||
@@ -237,6 +236,8 @@ test("lists compatible workspace summaries and reads a validated workspace", asy
|
||||
})]);
|
||||
expect(detail.statusCode).toBe(200);
|
||||
expect(detail.json()).toMatchObject({ workspace, revision });
|
||||
expect(list.json()[0].revision).not.toHaveProperty("state");
|
||||
expect(detail.json().revision).not.toHaveProperty("state");
|
||||
});
|
||||
|
||||
test("validates a canonical workspace and runs the injected installation diagnostic", async () => {
|
||||
@@ -278,36 +279,6 @@ test.each([
|
||||
expect(registry.publish).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("rejects a migration-required v2 workspace before resolving semantic diagnostics", async () => {
|
||||
const diagnose = vi.fn(async () => ({
|
||||
activatable: false,
|
||||
diagnostics: [{ level: "error" as const, code: "binding_missing" as const, field: "THT_WS_PSD_CLINICAL_VECTOR_BASE_URL", message: "Installation binding is missing or invalid." }],
|
||||
}));
|
||||
const registry = registryFake({
|
||||
read: vi.fn(async () => ({ workspace: workspaceV2, revision: { ...revision, state: "migration_required" as const } })),
|
||||
});
|
||||
const app = appFor(registry, diagnose);
|
||||
|
||||
const originalEnv = { ...process.env };
|
||||
process.env.THT_WS_PSD_CLINICAL_DWH_TRANSPORT = "rest_api";
|
||||
process.env.THT_WS_PSD_CLINICAL_DWH_BASE_URL = "https://dwh.example.test";
|
||||
process.env.THT_WS_PSD_CLINICAL_VECTOR_TRANSPORT = "rest_api";
|
||||
process.env.THT_WS_PSD_CLINICAL_VECTOR_BASE_URL = "https://vector.example.test";
|
||||
process.env.THT_WS_PSD_CLINICAL_EMBEDDING_BASE_URL = "https://embedding.example.test";
|
||||
try {
|
||||
const testResult = await app.inject({ method: "POST", url: "/workspaces/psd-clinical/test", payload: {} });
|
||||
|
||||
expect(testResult.statusCode).toBe(400);
|
||||
expect(testResult.json()).toEqual({
|
||||
code: "workspace_not_activatable",
|
||||
message: "Workspace cannot be activated on this installation.",
|
||||
});
|
||||
expect(diagnose).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
process.env = originalEnv;
|
||||
}
|
||||
});
|
||||
|
||||
test("runs diagnostics for a schema v3 workspace without external semantic bindings", async () => {
|
||||
const diagnose = vi.fn(async () => ({ activatable: true, diagnostics: [] }));
|
||||
const app = appFor(registryFake(), diagnose);
|
||||
|
||||
@@ -312,37 +312,47 @@ async function pushInvalidWorkspace(source: string): Promise<void> {
|
||||
await git(source, ["push", "origin", "main"]);
|
||||
}
|
||||
|
||||
function legacyDigest(contents: string): string {
|
||||
return createHash("sha256").update(contents).digest("hex");
|
||||
type HistoricalRevisionState = "absent" | "operational" | "migration_required" | "unknown";
|
||||
|
||||
function revisionWithHistoricalState(
|
||||
revision: Record<string, unknown>,
|
||||
encoding: HistoricalRevisionState,
|
||||
): Record<string, unknown> {
|
||||
const { state: _state, ...stateFree } = revision;
|
||||
return encoding === "absent" ? stateFree : {
|
||||
...stateFree,
|
||||
state: encoding === "unknown" ? "retired" : encoding,
|
||||
};
|
||||
}
|
||||
|
||||
function persistPreStateManifest(root: string, commit: string): void {
|
||||
const snapshotDirectory = join(root, "snapshots", commit);
|
||||
function rewritePersistedRevisionStates(
|
||||
root: string,
|
||||
commit: string,
|
||||
activeEncoding: HistoricalRevisionState,
|
||||
manifestEncoding: HistoricalRevisionState,
|
||||
): void {
|
||||
const activePath = join(root, "state", "active.json");
|
||||
const snapshotPath = join(snapshotDirectory, "snapshot.json");
|
||||
const snapshotPath = join(root, "snapshots", commit, "snapshot.json");
|
||||
const active = JSON.parse(readFileSync(activePath, "utf8"));
|
||||
const manifest = JSON.parse(readFileSync(snapshotPath, "utf8"));
|
||||
const envName = "psd-clinical.env.example";
|
||||
const docsName = "psd-clinical.md";
|
||||
const envExample = "# Legacy registry artifact\n";
|
||||
const markdown = "# Legacy registry artifact\n";
|
||||
|
||||
chmodSync(join(snapshotDirectory, envName), 0o600);
|
||||
chmodSync(join(snapshotDirectory, docsName), 0o600);
|
||||
writeFileSync(join(snapshotDirectory, envName), envExample);
|
||||
writeFileSync(join(snapshotDirectory, docsName), markdown);
|
||||
active.revisions = active.revisions.map(({ state: _state, ...revision }: Record<string, unknown>) => revision);
|
||||
manifest.revisions = manifest.revisions.map(({ state: _state, ...revision }: Record<string, unknown>) => revision);
|
||||
manifest.files = {
|
||||
"psd-clinical.yaml": manifest.files["psd-clinical.yaml"],
|
||||
[envName]: legacyDigest(envExample),
|
||||
[docsName]: legacyDigest(markdown),
|
||||
};
|
||||
active.revisions = active.revisions.map((revision: Record<string, unknown>) => (
|
||||
revisionWithHistoricalState(revision, activeEncoding)
|
||||
));
|
||||
manifest.revisions = manifest.revisions.map((revision: Record<string, unknown>) => (
|
||||
revisionWithHistoricalState(revision, manifestEncoding)
|
||||
));
|
||||
writeFileSync(activePath, JSON.stringify(active));
|
||||
chmodSync(snapshotPath, 0o600);
|
||||
writeFileSync(snapshotPath, JSON.stringify(manifest));
|
||||
}
|
||||
|
||||
function persistedState(root: string, commit: string): { active: any; manifest: any } {
|
||||
return {
|
||||
active: JSON.parse(readFileSync(join(root, "state", "active.json"), "utf8")),
|
||||
manifest: JSON.parse(readFileSync(join(root, "snapshots", commit, "snapshot.json"), "utf8")),
|
||||
};
|
||||
}
|
||||
|
||||
test("allows first API publication and delete-last from a content-only registry base", async () => {
|
||||
const remote = await contentOnlyFixture();
|
||||
const registry = new WorkspaceRegistry(config(join(remote.root, "registry"), remote.remote));
|
||||
@@ -390,7 +400,6 @@ test("concurrent first lists lazily bootstrap a clean registry once safely", asy
|
||||
expect.objectContaining({
|
||||
id: "psd-clinical",
|
||||
commit: remote.initialCommit,
|
||||
state: "operational",
|
||||
}),
|
||||
]);
|
||||
}
|
||||
@@ -706,63 +715,171 @@ test.each([
|
||||
expect(existsSync(join(remote.root, "registry", "state", "active.json"))).toBe(false);
|
||||
});
|
||||
|
||||
test("migrates a validated pre-state manifest while preserving its v3 operational state", async () => {
|
||||
test("writes only state-free revisions and never exposes revision state", async () => {
|
||||
const remote = await fixture();
|
||||
const root = join(remote.root, "registry");
|
||||
const firstRegistry = new WorkspaceRegistry(config(root, remote.remote));
|
||||
await firstRegistry.bootstrap();
|
||||
persistPreStateManifest(root, remote.initialCommit);
|
||||
const registry = new WorkspaceRegistry(config(root, remote.remote));
|
||||
await registry.bootstrap();
|
||||
|
||||
const restoredRegistry = new WorkspaceRegistry(config(root, remote.remote));
|
||||
await expect(restoredRegistry.bootstrap()).resolves.toMatchObject({
|
||||
head: remote.initialCommit,
|
||||
degraded: false,
|
||||
const initial = persistedState(root, remote.initialCommit);
|
||||
expect(Object.keys(initial.active).sort()).toEqual(["head", "revisions"]);
|
||||
expect(Object.keys(initial.manifest).sort()).toEqual(["files", "head", "revisions"]);
|
||||
expect(Object.keys(initial.active.revisions[0]).sort()).toEqual(["blob", "commit", "id", "snapshotPath"]);
|
||||
expect(Object.keys(initial.manifest.revisions[0]).sort()).toEqual(["blob", "commit", "id", "snapshotPath"]);
|
||||
|
||||
const listed = await registry.list();
|
||||
const read = await registry.read("psd-clinical");
|
||||
expect(listed[0]).not.toHaveProperty("state");
|
||||
expect(read.revision).not.toHaveProperty("state");
|
||||
|
||||
const published = await registry.publish({
|
||||
action: "update",
|
||||
workspace: workspaceWith("psd-clinical", { name: "State-free revision" }),
|
||||
baseCommit: remote.initialCommit,
|
||||
baseBlob: listed[0]!.blob,
|
||||
});
|
||||
await expect(restoredRegistry.list()).resolves.toMatchObject([
|
||||
{ id: "psd-clinical", state: "operational" },
|
||||
]);
|
||||
|
||||
const active = JSON.parse(readFileSync(join(root, "state", "active.json"), "utf8"));
|
||||
const manifest = JSON.parse(readFileSync(join(root, "snapshots", remote.initialCommit, "snapshot.json"), "utf8"));
|
||||
expect(active.revisions[0].state).toBe("operational");
|
||||
expect(manifest.revisions[0].state).toBe("operational");
|
||||
expect(Object.keys(manifest.files).sort()).toEqual([
|
||||
"psd-clinical.env.example", "psd-clinical.md", "psd-clinical.yaml",
|
||||
]);
|
||||
const updated = persistedState(root, published!.commit);
|
||||
expect(updated.active.revisions[0]).not.toHaveProperty("state");
|
||||
expect(updated.manifest.revisions[0]).not.toHaveProperty("state");
|
||||
expect(published).not.toHaveProperty("state");
|
||||
});
|
||||
|
||||
test("finishes a pre-state active manifest migration after its v3 snapshot was atomically updated", async () => {
|
||||
test("accepts historical operational state without leaking it or rewriting the immutable snapshot", async () => {
|
||||
const remote = await fixture();
|
||||
const root = join(remote.root, "registry");
|
||||
await new WorkspaceRegistry(config(root, remote.remote)).bootstrap();
|
||||
rewritePersistedRevisionStates(root, remote.initialCommit, "operational", "operational");
|
||||
const snapshotPath = join(root, "snapshots", remote.initialCommit, "snapshot.json");
|
||||
const historicalManifest = readFileSync(snapshotPath, "utf8");
|
||||
|
||||
const restored = new WorkspaceRegistry(config(root, remote.remote));
|
||||
const listed = await restored.list();
|
||||
const read = await restored.read("psd-clinical");
|
||||
|
||||
expect(listed[0]).not.toHaveProperty("state");
|
||||
expect(read.revision).not.toHaveProperty("state");
|
||||
expect(readFileSync(snapshotPath, "utf8")).toBe(historicalManifest);
|
||||
|
||||
await restored.bootstrap();
|
||||
const rewrittenActive = persistedState(root, remote.initialCommit).active;
|
||||
expect(rewrittenActive.revisions[0]).not.toHaveProperty("state");
|
||||
expect(readFileSync(snapshotPath, "utf8")).toBe(historicalManifest);
|
||||
});
|
||||
|
||||
test.each([
|
||||
["historical active and state-free snapshot", "operational", "absent"],
|
||||
["state-free active and historical snapshot", "absent", "operational"],
|
||||
] as const)("normalizes mixed persisted revision encodings: %s", async (_name, activeState, manifestState) => {
|
||||
const remote = await fixture();
|
||||
const root = join(remote.root, "registry");
|
||||
await new WorkspaceRegistry(config(root, remote.remote)).bootstrap();
|
||||
rewritePersistedRevisionStates(root, remote.initialCommit, activeState, manifestState);
|
||||
const snapshotPath = join(root, "snapshots", remote.initialCommit, "snapshot.json");
|
||||
const historicalManifest = readFileSync(snapshotPath, "utf8");
|
||||
|
||||
const revisions = await new WorkspaceRegistry(config(root, remote.remote)).list();
|
||||
|
||||
expect(revisions[0]).not.toHaveProperty("state");
|
||||
expect(readFileSync(snapshotPath, "utf8")).toBe(historicalManifest);
|
||||
});
|
||||
|
||||
test.each([
|
||||
["migration_required in active state", "migration_required", "absent"],
|
||||
["migration_required in snapshot manifest", "absent", "migration_required"],
|
||||
["unknown state in active state", "unknown", "operational"],
|
||||
["unknown state in snapshot manifest", "operational", "unknown"],
|
||||
] as const)("rejects %s", async (_name, activeState, manifestState) => {
|
||||
const remote = await fixture();
|
||||
const root = join(remote.root, "registry");
|
||||
await new WorkspaceRegistry(config(root, remote.remote)).bootstrap();
|
||||
rewritePersistedRevisionStates(root, remote.initialCommit, activeState, manifestState);
|
||||
|
||||
await expect(new WorkspaceRegistry(config(root, remote.remote)).list()).rejects.toMatchObject({
|
||||
code: "workspace_invalid",
|
||||
});
|
||||
});
|
||||
|
||||
test.each([
|
||||
["active top level", "active", "top"],
|
||||
["active revision", "active", "revision"],
|
||||
["snapshot top level", "manifest", "top"],
|
||||
["snapshot revision", "manifest", "revision"],
|
||||
] as const)("rejects unknown fields in %s", async (_name, component, location) => {
|
||||
const remote = await fixture();
|
||||
const root = join(remote.root, "registry");
|
||||
await new WorkspaceRegistry(config(root, remote.remote)).bootstrap();
|
||||
const path = component === "active"
|
||||
? join(root, "state", "active.json")
|
||||
: join(root, "snapshots", remote.initialCommit, "snapshot.json");
|
||||
const persisted = JSON.parse(readFileSync(path, "utf8"));
|
||||
if (location === "top") persisted.unexpected = true;
|
||||
else persisted.revisions[0].unexpected = true;
|
||||
if (component === "manifest") chmodSync(path, 0o600);
|
||||
writeFileSync(path, JSON.stringify(persisted));
|
||||
|
||||
await expect(new WorkspaceRegistry(config(root, remote.remote)).list()).rejects.toMatchObject({
|
||||
code: "workspace_invalid",
|
||||
});
|
||||
});
|
||||
|
||||
test("normalizes operational state in retained historical snapshots without rewriting them", async () => {
|
||||
const remote = await fixture();
|
||||
const root = join(remote.root, "registry");
|
||||
const registry = new WorkspaceRegistry(config(root, remote.remote));
|
||||
await registry.bootstrap();
|
||||
persistPreStateManifest(root, remote.initialCommit);
|
||||
|
||||
writeFileSync(join(remote.source, "workspaces", "psd-clinical.yaml"), validYaml.replace(
|
||||
"name: Policlinico San Donato", "name: Current workspace",
|
||||
));
|
||||
await git(remote.source, ["add", "workspaces/psd-clinical.yaml"]);
|
||||
await git(remote.source, ["commit", "-m", "Update active workspace"]);
|
||||
await git(remote.source, ["push", "origin", "main"]);
|
||||
await registry.pull();
|
||||
rewritePersistedRevisionStates(root, remote.initialCommit, "absent", "operational");
|
||||
const snapshotPath = join(root, "snapshots", remote.initialCommit, "snapshot.json");
|
||||
const manifest = JSON.parse(readFileSync(snapshotPath, "utf8"));
|
||||
manifest.revisions[0].state = "operational";
|
||||
writeFileSync(snapshotPath, JSON.stringify(manifest));
|
||||
const historicalManifest = readFileSync(snapshotPath, "utf8");
|
||||
|
||||
const restoredRegistry = new WorkspaceRegistry(config(root, remote.remote));
|
||||
await expect(restoredRegistry.list()).resolves.toMatchObject([
|
||||
{ id: "psd-clinical", state: "operational" },
|
||||
]);
|
||||
const retained = await new WorkspaceRegistry(config(root, remote.remote)).listRetainedSnapshots();
|
||||
|
||||
expect(retained).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ id: "psd-clinical", commit: remote.initialCommit }),
|
||||
]));
|
||||
expect(retained.every((revision) => !("state" in revision))).toBe(true);
|
||||
expect(readFileSync(snapshotPath, "utf8")).toBe(historicalManifest);
|
||||
});
|
||||
|
||||
test("rejects a corrupt pre-state manifest rather than accepting it during migration", async () => {
|
||||
test("normalizes historical operational state during offline fallback after restart", async () => {
|
||||
const remote = await fixture();
|
||||
const root = join(remote.root, "registry");
|
||||
const registry = new WorkspaceRegistry(config(root, remote.remote));
|
||||
await registry.bootstrap();
|
||||
persistPreStateManifest(root, remote.initialCommit);
|
||||
const snapshotPath = join(root, "snapshots", remote.initialCommit, "snapshot.json");
|
||||
const manifest = JSON.parse(readFileSync(snapshotPath, "utf8"));
|
||||
manifest.files["psd-clinical.yaml"] = "0".repeat(64);
|
||||
writeFileSync(snapshotPath, JSON.stringify(manifest));
|
||||
await new WorkspaceRegistry(config(root, remote.remote)).bootstrap();
|
||||
rewritePersistedRevisionStates(root, remote.initialCommit, "operational", "operational");
|
||||
rmSync(remote.remote, { recursive: true, force: true });
|
||||
|
||||
const restoredRegistry = new WorkspaceRegistry(config(root, remote.remote));
|
||||
await expect(restoredRegistry.bootstrap()).rejects.toMatchObject({ code: "workspace_invalid" });
|
||||
await expect(restoredRegistry.list()).rejects.toMatchObject({ code: "workspace_invalid" });
|
||||
const restored = new WorkspaceRegistry(config(root, remote.remote));
|
||||
await expect(restored.pull()).resolves.toMatchObject({ degraded: true, head: remote.initialCommit });
|
||||
const listed = await restored.list();
|
||||
const read = await restored.read("psd-clinical");
|
||||
expect(listed[0]).not.toHaveProperty("state");
|
||||
expect(read.revision).not.toHaveProperty("state");
|
||||
});
|
||||
|
||||
test("fails closed when a retained snapshot descriptor is not schema v3", async () => {
|
||||
const remote = await fixture();
|
||||
const root = join(remote.root, "registry");
|
||||
await new WorkspaceRegistry(config(root, remote.remote)).bootstrap();
|
||||
const snapshotDirectory = join(root, "snapshots", remote.initialCommit);
|
||||
const yamlPath = join(snapshotDirectory, "psd-clinical.yaml");
|
||||
chmodSync(yamlPath, 0o600);
|
||||
const legacy = legacyV2Yaml();
|
||||
writeFileSync(yamlPath, legacy);
|
||||
const manifestPath = join(snapshotDirectory, "snapshot.json");
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
||||
manifest.files["psd-clinical.yaml"] = createHash("sha256").update(legacy).digest("hex");
|
||||
chmodSync(manifestPath, 0o600);
|
||||
writeFileSync(manifestPath, JSON.stringify(manifest));
|
||||
|
||||
await expect(new WorkspaceRegistry(config(root, remote.remote)).list()).rejects.toMatchObject({
|
||||
code: "workspace_invalid",
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps the last valid snapshot when a pulled commit has invalid YAML", async () => {
|
||||
@@ -889,8 +1006,8 @@ test("lists operational descriptors retained after their workspace was removed f
|
||||
|
||||
const retained = await registry.listRetainedSnapshots();
|
||||
expect(retained).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ id: "psd-clinical", commit: remote.initialCommit, state: "operational" }),
|
||||
expect.objectContaining({ id: "archive-only", state: "operational" }),
|
||||
expect.objectContaining({ id: "psd-clinical", commit: remote.initialCommit }),
|
||||
expect.objectContaining({ id: "archive-only" }),
|
||||
]));
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user