fix: migrate pre-state workspace manifests
This commit is contained in:
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user