fix: migrate pre-state workspace manifests

This commit is contained in:
2026-08-03 23:39:06 +02:00
parent 450d7ab07f
commit 6ab80d8a38
2 changed files with 265 additions and 21 deletions
+100
View File
@@ -1,4 +1,5 @@
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import {
chmodSync, existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync,
} from "node:fs";
@@ -91,6 +92,35 @@ 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");
}
function persistPreStateManifest(root: string, commit: string): void {
const snapshotDirectory = join(root, "snapshots", commit);
const activePath = join(root, "state", "active.json");
const snapshotPath = join(snapshotDirectory, "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";
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),
};
writeFileSync(activePath, JSON.stringify(active));
chmodSync(snapshotPath, 0o600);
writeFileSync(snapshotPath, JSON.stringify(manifest));
}
test("bootstraps a checkout and activates a validated immutable snapshot", async () => {
const remote = await fixture();
const registry = new WorkspaceRegistry(config(join(remote.root, "registry"), remote.remote));
@@ -123,6 +153,76 @@ test("lists a v1 descriptor in migration-required state without rendering operat
expect(existsSync(join(remote.root, "registry", "snapshots", status.head!, "psd-clinical.md"))).toBe(false);
});
test("migrates a validated pre-state manifest and keeps its v1 workspace migration-gated", async () => {
const legacyYaml = validYaml.replace(
" database: postgres\n schema: vectors\n",
"",
).replace("schema_version: 2", "schema_version: 1");
const remote = await fixture(legacyYaml);
const root = join(remote.root, "registry");
const firstRegistry = new WorkspaceRegistry(config(root, remote.remote));
await firstRegistry.bootstrap();
persistPreStateManifest(root, remote.initialCommit);
const restoredRegistry = new WorkspaceRegistry(config(root, remote.remote));
await expect(restoredRegistry.bootstrap()).resolves.toMatchObject({
head: remote.initialCommit,
degraded: false,
});
await expect(restoredRegistry.list()).resolves.toMatchObject([
{ id: "psd-clinical", state: "migration_required" },
]);
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("migration_required");
expect(manifest.revisions[0].state).toBe("migration_required");
expect(Object.keys(manifest.files)).toEqual(["psd-clinical.yaml"]);
});
test("finishes a pre-state active manifest migration after its snapshot was atomically updated", async () => {
const legacyYaml = validYaml.replace(
" database: postgres\n schema: vectors\n",
"",
).replace("schema_version: 2", "schema_version: 1");
const remote = await fixture(legacyYaml);
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.revisions[0].state = "migration_required";
manifest.files = { "psd-clinical.yaml": manifest.files["psd-clinical.yaml"] };
writeFileSync(snapshotPath, JSON.stringify(manifest));
const restoredRegistry = new WorkspaceRegistry(config(root, remote.remote));
await expect(restoredRegistry.list()).resolves.toMatchObject([
{ id: "psd-clinical", state: "migration_required" },
]);
});
test("rejects a corrupt pre-state manifest rather than accepting it during migration", async () => {
const legacyYaml = validYaml.replace(
" database: postgres\n schema: vectors\n",
"",
).replace("schema_version: 2", "schema_version: 1");
const remote = await fixture(legacyYaml);
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));
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" });
});
test("keeps the last valid snapshot when a pulled commit has invalid YAML", async () => {
const remote = await fixture();
const registry = new WorkspaceRegistry(config(join(remote.root, "registry"), remote.remote));