feat: bind evidence trees to registry revisions
This commit is contained in:
@@ -36,6 +36,14 @@ llm_policy:
|
||||
allowed: [zai/glm-5.2]
|
||||
`;
|
||||
|
||||
function withFilesystemEvidence(source: string, id = "psd-clinical"): string {
|
||||
return source.concat(`evidence:
|
||||
source:
|
||||
type: filesystem
|
||||
uri: workspace-content/${id}/evidence
|
||||
`);
|
||||
}
|
||||
|
||||
function withDwhRestTransport(source: string): string {
|
||||
return source.replace(
|
||||
"supported_transports: [postgres_direct]",
|
||||
@@ -181,7 +189,15 @@ async function fixture(workspaceSource = validYaml): Promise<{
|
||||
await git(source, ["config", "user.email", "workspace-registry@example.invalid"]);
|
||||
mkdirSync(join(source, "workspaces"));
|
||||
writeFileSync(join(source, "workspaces", "psd-clinical.yaml"), workspaceSource);
|
||||
await git(source, ["add", "workspaces/psd-clinical.yaml"]);
|
||||
if (workspaceSource.includes("type: filesystem")) {
|
||||
mkdirSync(join(source, "workspace-content", "psd-clinical", "evidence"), { recursive: true });
|
||||
mkdirSync(join(source, "workspace-content", "research", "evidence"), { recursive: true });
|
||||
writeFileSync(join(source, "workspace-content", "psd-clinical", "evidence", "guide.md"), "guide v1\n");
|
||||
writeFileSync(join(source, "workspace-content", "research", "evidence", "guide.md"), "research guide\n");
|
||||
await git(source, ["add", "workspaces", "workspace-content"]);
|
||||
} else {
|
||||
await git(source, ["add", "workspaces/psd-clinical.yaml"]);
|
||||
}
|
||||
await git(source, ["commit", "-m", "Initial workspace"]);
|
||||
await git(source, ["remote", "add", "origin", remote]);
|
||||
await git(source, ["push", "origin", "main"]);
|
||||
@@ -247,6 +263,16 @@ function workspaceWith(
|
||||
};
|
||||
}
|
||||
|
||||
function filesystemWorkspace(id: string): CanonicalWorkspace {
|
||||
return parseWorkspaceYaml(withFilesystemEvidence(
|
||||
validYaml
|
||||
.replace("id: psd-clinical", `id: ${id}`)
|
||||
.replace("name: Policlinico San Donato", `name: ${id}`)
|
||||
.replace("collection: psd-clinical", `collection: ${id}`),
|
||||
id,
|
||||
)) as CanonicalWorkspace;
|
||||
}
|
||||
|
||||
async function checkoutStatus(checkout: string): Promise<{ porcelain: string; divergence: string }> {
|
||||
return {
|
||||
porcelain: await gitOutput(checkout, ["status", "--porcelain"]),
|
||||
@@ -321,6 +347,144 @@ test("concurrent first lists lazily bootstrap a clean registry once safely", asy
|
||||
expect(existsSync(join(root, "state", "active.json"))).toBe(true);
|
||||
});
|
||||
|
||||
test("publishes a filesystem descriptor only when its Evidence tree exists in the pulled base", async () => {
|
||||
const remote = await fixture(withFilesystemEvidence(validYaml));
|
||||
const root = join(remote.root, "registry");
|
||||
const registry = new WorkspaceRegistry(config(root, remote.remote));
|
||||
await registry.bootstrap();
|
||||
const evidencePath = "workspace-content/research/evidence";
|
||||
const initialTree = await gitOutput(remote.root, [
|
||||
"--git-dir", remote.remote, "rev-parse", `${remote.initialCommit}:${evidencePath}`,
|
||||
]);
|
||||
|
||||
const created = await registry.publish({
|
||||
action: "create",
|
||||
workspace: filesystemWorkspace("research"),
|
||||
baseCommit: remote.initialCommit,
|
||||
});
|
||||
|
||||
expect(created?.commit).not.toBe(remote.initialCommit);
|
||||
await expect(runFile("git", [
|
||||
"--git-dir", remote.remote, "cat-file", "-e", `${created!.commit}:workspaces/research.yaml`,
|
||||
], { cwd: remote.root })).resolves.toBeDefined();
|
||||
await expect(runFile("git", [
|
||||
"--git-dir", remote.remote, "cat-file", "-e", `${created!.commit}:${evidencePath}/guide.md`,
|
||||
], { cwd: remote.root })).resolves.toBeDefined();
|
||||
expect(await gitOutput(remote.root, [
|
||||
"--git-dir", remote.remote, "rev-parse", `${created!.commit}:${evidencePath}`,
|
||||
])).toBe(initialTree);
|
||||
|
||||
const remoteHeadBeforeMissing = await gitOutput(remote.root, ["--git-dir", remote.remote, "rev-parse", "HEAD"]);
|
||||
await expect(registry.publish({
|
||||
action: "create",
|
||||
workspace: filesystemWorkspace("missing-tree"),
|
||||
baseCommit: created!.commit,
|
||||
})).rejects.toMatchObject({ code: "workspace_invalid" });
|
||||
expect(await gitOutput(remote.root, ["--git-dir", remote.remote, "rev-parse", "HEAD"])).toBe(
|
||||
remoteHeadBeforeMissing,
|
||||
);
|
||||
expect(await checkoutStatus(join(root, "repo"))).toEqual({ porcelain: "", divergence: "0\t0" });
|
||||
});
|
||||
|
||||
test.each(["missing", "blob"])(
|
||||
"rejects a remote filesystem descriptor with a %s Evidence root and keeps the active snapshot",
|
||||
async (invalidKind) => {
|
||||
const remote = await fixture(withFilesystemEvidence(validYaml));
|
||||
const registry = new WorkspaceRegistry(config(join(remote.root, "registry"), remote.remote));
|
||||
await registry.bootstrap();
|
||||
const evidenceRoot = join(remote.source, "workspace-content", "psd-clinical", "evidence");
|
||||
rmSync(evidenceRoot, { recursive: true, force: true });
|
||||
if (invalidKind === "blob") writeFileSync(evidenceRoot, "not a tree\n");
|
||||
await git(remote.source, ["add", "-A", "workspace-content/psd-clinical/evidence"]);
|
||||
await git(remote.source, ["commit", "-m", `Make Evidence root ${invalidKind}`]);
|
||||
await git(remote.source, ["push", "origin", "main"]);
|
||||
const invalidCommit = await gitOutput(remote.source, ["rev-parse", "HEAD"]);
|
||||
|
||||
await expect(registry.pull()).rejects.toMatchObject({ code: "workspace_invalid" });
|
||||
expect(invalidCommit).not.toBe(remote.initialCommit);
|
||||
await expect(registry.read("psd-clinical")).resolves.toMatchObject({
|
||||
revision: { commit: remote.initialCommit },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("creates an immutable descriptor revision for a content-only Evidence commit", async () => {
|
||||
const remote = await fixture(withFilesystemEvidence(validYaml));
|
||||
const root = join(remote.root, "registry");
|
||||
const registry = new WorkspaceRegistry(config(root, remote.remote));
|
||||
await registry.bootstrap();
|
||||
const initial = await registry.read("psd-clinical");
|
||||
const evidencePath = "workspace-content/psd-clinical/evidence";
|
||||
const initialTree = await gitOutput(remote.source, ["rev-parse", `${remote.initialCommit}:${evidencePath}`]);
|
||||
writeFileSync(join(remote.source, evidencePath, "guide.md"), "guide v2\n");
|
||||
await git(remote.source, ["add", `${evidencePath}/guide.md`]);
|
||||
await git(remote.source, ["commit", "-m", "Update Evidence only"]);
|
||||
await git(remote.source, ["push", "origin", "main"]);
|
||||
const contentCommit = await gitOutput(remote.source, ["rev-parse", "HEAD"]);
|
||||
const contentTree = await gitOutput(remote.source, ["rev-parse", `${contentCommit}:${evidencePath}`]);
|
||||
|
||||
await registry.pull();
|
||||
const current = await registry.read("psd-clinical");
|
||||
|
||||
expect(contentTree).not.toBe(initialTree);
|
||||
expect(current.revision).toMatchObject({ commit: contentCommit, blob: initial.revision.blob });
|
||||
expect(current.revision.snapshotPath).not.toBe(initial.revision.snapshotPath);
|
||||
expect(readFileSync(current.revision.snapshotPath, "utf8")).toBe(
|
||||
readFileSync(initial.revision.snapshotPath, "utf8"),
|
||||
);
|
||||
await expect(runFile("git", [
|
||||
"--git-dir", remote.remote, "cat-file", "-e", `${contentCommit}:${evidencePath}/guide.md`,
|
||||
], { cwd: remote.root })).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
test("rejects a stale API update after a content-only Evidence commit", async () => {
|
||||
const remote = await fixture(withFilesystemEvidence(validYaml));
|
||||
const registry = new WorkspaceRegistry(config(join(remote.root, "registry"), remote.remote));
|
||||
await registry.bootstrap();
|
||||
const initial = await registry.read("psd-clinical");
|
||||
const guide = join(remote.source, "workspace-content", "psd-clinical", "evidence", "guide.md");
|
||||
writeFileSync(guide, "curator content\n");
|
||||
await git(remote.source, ["add", "workspace-content/psd-clinical/evidence/guide.md"]);
|
||||
await git(remote.source, ["commit", "-m", "Curator Evidence update"]);
|
||||
await git(remote.source, ["push", "origin", "main"]);
|
||||
const curatorCommit = await gitOutput(remote.source, ["rev-parse", "HEAD"]);
|
||||
|
||||
await expect(registry.publish({
|
||||
action: "update",
|
||||
workspace: filesystemWorkspace("psd-clinical"),
|
||||
baseCommit: initial.revision.commit,
|
||||
baseBlob: initial.revision.blob,
|
||||
})).rejects.toMatchObject({
|
||||
code: "workspace_conflict",
|
||||
expected: { commit: initial.revision.commit, blob: initial.revision.blob },
|
||||
actual: { commit: curatorCommit, blob: initial.revision.blob },
|
||||
});
|
||||
expect(await gitOutput(remote.root, ["--git-dir", remote.remote, "rev-parse", "HEAD"])).toBe(curatorCommit);
|
||||
});
|
||||
|
||||
test("keeps content-only historical descriptor revisions distinguishable by commit", async () => {
|
||||
const remote = await fixture(withFilesystemEvidence(validYaml));
|
||||
const registry = new WorkspaceRegistry(config(join(remote.root, "registry"), remote.remote));
|
||||
await registry.bootstrap();
|
||||
writeFileSync(
|
||||
join(remote.source, "workspace-content", "psd-clinical", "evidence", "guide.md"),
|
||||
"historical content\n",
|
||||
);
|
||||
await git(remote.source, ["add", "workspace-content/psd-clinical/evidence/guide.md"]);
|
||||
await git(remote.source, ["commit", "-m", "Retained Evidence update"]);
|
||||
await git(remote.source, ["push", "origin", "main"]);
|
||||
const contentCommit = await gitOutput(remote.source, ["rev-parse", "HEAD"]);
|
||||
await registry.pull();
|
||||
await registry.reconcileSnapshotRetention([remote.initialCommit]);
|
||||
|
||||
const retained = (await registry.listRetainedSnapshots()).filter(({ id }) => id === "psd-clinical");
|
||||
expect(retained.map(({ commit }) => commit)).toEqual([contentCommit, remote.initialCommit]);
|
||||
const oldPinned = await registry.readPinned("psd-clinical", remote.initialCommit);
|
||||
const newPinned = await registry.readPinned("psd-clinical", contentCommit);
|
||||
expect(oldPinned.workspaceConfigPath).not.toBe(newPinned.workspaceConfigPath);
|
||||
expect(oldPinned.workspace).toEqual(newPinned.workspace);
|
||||
});
|
||||
|
||||
test("publishes create, update, and delete with the configured Git author identity", async () => {
|
||||
const remote = await fixture();
|
||||
const registry = new WorkspaceRegistry(config(join(remote.root, "registry"), remote.remote, {
|
||||
|
||||
Reference in New Issue
Block a user