feat: preserve evidence in workspace artifacts
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
chmodSync, existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync,
|
||||
chmodSync, existsSync, mkdtempSync, mkdirSync, readdirSync, readFileSync, rmSync, symlinkSync, writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
@@ -9,7 +9,9 @@ import { promisify } from "node:util";
|
||||
import { afterEach, expect, test } from "vitest";
|
||||
import { WorkspaceRepositoryLock } from "../src/workspaces/git-repository.js";
|
||||
import { WorkspaceRegistry } from "../src/workspaces/registry.js";
|
||||
import { parseWorkspaceYaml, type CanonicalWorkspace } from "../src/workspaces/schema.js";
|
||||
import {
|
||||
parseWorkspaceYaml, renderWorkspaceDocs, serializeWorkspaceYaml, type CanonicalWorkspace,
|
||||
} from "../src/workspaces/schema.js";
|
||||
import type { WorkspaceRegistryConfig } from "../src/workspaces/types.js";
|
||||
|
||||
const validYaml = `workspace:
|
||||
@@ -988,3 +990,96 @@ test("rejects a corrupt fallback snapshot instead of returning degraded active s
|
||||
|
||||
await expect(registry.pull()).rejects.toMatchObject({ code: "workspace_invalid" });
|
||||
});
|
||||
|
||||
|
||||
test("snapshots canonical Evidence artifacts at the active commit without copying Evidence bytes", async () => {
|
||||
const remote = await fixture(withFilesystemEvidence(validYaml));
|
||||
const registryRoot = join(remote.root, "registry");
|
||||
const registry = new WorkspaceRegistry(config(registryRoot, remote.remote));
|
||||
|
||||
const status = await registry.bootstrap();
|
||||
const active = await registry.read("psd-clinical");
|
||||
const snapshotDirectory = join(registryRoot, "snapshots", status.head!);
|
||||
const descriptor = active.workspace as CanonicalWorkspace;
|
||||
const docs = renderWorkspaceDocs(descriptor);
|
||||
const expectedFiles: Record<string, string> = {
|
||||
"psd-clinical.yaml": serializeWorkspaceYaml(descriptor),
|
||||
"psd-clinical.env.example": docs.envExample,
|
||||
"psd-clinical.md": docs.markdown,
|
||||
};
|
||||
const manifest = JSON.parse(readFileSync(join(snapshotDirectory, "snapshot.json"), "utf8"));
|
||||
|
||||
expect(status.head).toBe(remote.initialCommit);
|
||||
const committedDescriptor = parseWorkspaceYaml(await gitOutput(remote.source, [
|
||||
"show", `${remote.initialCommit}:workspaces/psd-clinical.yaml`,
|
||||
])) as CanonicalWorkspace;
|
||||
expect(expectedFiles["psd-clinical.yaml"]).toBe(serializeWorkspaceYaml(committedDescriptor));
|
||||
expect(readdirSync(snapshotDirectory).sort()).toEqual([
|
||||
"psd-clinical.env.example", "psd-clinical.md", "psd-clinical.yaml", "snapshot.json",
|
||||
]);
|
||||
expect(manifest.head).toBe(remote.initialCommit);
|
||||
expect(manifest.revisions[0]).toMatchObject({
|
||||
id: "psd-clinical", commit: remote.initialCommit, blob: active.revision.blob,
|
||||
});
|
||||
expect(Object.keys(manifest.files).sort()).toEqual(Object.keys(expectedFiles).sort());
|
||||
for (const [name, contents] of Object.entries(expectedFiles)) {
|
||||
expect(readFileSync(join(snapshotDirectory, name), "utf8")).toBe(contents);
|
||||
expect(manifest.files[name]).toBe(createHash("sha256").update(contents).digest("hex"));
|
||||
}
|
||||
expect(JSON.stringify(manifest)).not.toContain("workspace-content/");
|
||||
expect(readdirSync(snapshotDirectory).some((name) => name === "workspace-content")).toBe(false);
|
||||
expect(readFileSync(join(remote.source, "workspace-content/psd-clinical/evidence/guide.md"), "utf8"))
|
||||
.toBe("guide v1\n");
|
||||
});
|
||||
|
||||
test("never copies an installation secret canary into Git, generated artifacts, metadata, or errors", async () => {
|
||||
const remote = await fixture(validYaml.concat(`evidence:
|
||||
source:
|
||||
type: http
|
||||
uris: [https://evidence.example.test/guide.md]
|
||||
authentication: signed_urls_file
|
||||
`));
|
||||
const canary = "CANARY-EVIDENCE-SECRET-ONLY-IN-FIXTURE";
|
||||
const secretDirectory = join(remote.root, "fixture-secrets");
|
||||
mkdirSync(secretDirectory);
|
||||
const secretFile = join(secretDirectory, "signed-urls");
|
||||
writeFileSync(secretFile, canary);
|
||||
const registryRoot = join(remote.root, "registry");
|
||||
const registry = new WorkspaceRegistry(config(registryRoot, remote.remote, {
|
||||
secretRoots: [secretDirectory],
|
||||
}));
|
||||
const previous = process.env.THT_WS_PSD_CLINICAL_EVIDENCE_SIGNED_URLS_FILE;
|
||||
process.env.THT_WS_PSD_CLINICAL_EVIDENCE_SIGNED_URLS_FILE = secretFile;
|
||||
try {
|
||||
const status = await registry.bootstrap();
|
||||
const snapshotDirectory = join(registryRoot, "snapshots", status.head!);
|
||||
let gitBlobText = "";
|
||||
try {
|
||||
gitBlobText = (await runFile(
|
||||
"git", ["grep", "-I", "-h", "-e", canary, "HEAD", "--", "."], { cwd: remote.source },
|
||||
)).stdout;
|
||||
} catch (error) {
|
||||
if (!error || typeof error !== "object" || !("code" in error) || error.code !== 1) throw error;
|
||||
gitBlobText = "stdout" in error ? String(error.stdout ?? "") : "";
|
||||
}
|
||||
expect(gitBlobText).toBe("");
|
||||
for (const name of readdirSync(snapshotDirectory)) {
|
||||
expect(readFileSync(join(snapshotDirectory, name), "utf8")).not.toContain(canary);
|
||||
}
|
||||
let thrown: unknown;
|
||||
try {
|
||||
await registry.publish({
|
||||
action: "create",
|
||||
workspace: filesystemWorkspace("missing-secret-canary-tree"),
|
||||
baseCommit: status.head!,
|
||||
});
|
||||
} catch (error) {
|
||||
thrown = error;
|
||||
}
|
||||
expect(thrown).toMatchObject({ code: "workspace_invalid" });
|
||||
expect(String(thrown)).not.toContain(canary);
|
||||
} finally {
|
||||
if (previous === undefined) delete process.env.THT_WS_PSD_CLINICAL_EVIDENCE_SIGNED_URLS_FILE;
|
||||
else process.env.THT_WS_PSD_CLINICAL_EVIDENCE_SIGNED_URLS_FILE = previous;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user