817 lines
32 KiB
TypeScript
817 lines
32 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import { createHash } from "node:crypto";
|
|
import { once } from "node:events";
|
|
import { Buffer } from "node:buffer";
|
|
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { promisify } from "node:util";
|
|
import { afterEach, expect, test, vi } from "vitest";
|
|
import yauzl from "yauzl";
|
|
import yazl from "yazl";
|
|
import { buildApp } from "../src/app.js";
|
|
import { loadConfig } from "../src/config.js";
|
|
import { createProductionWorkspaceDiagnoser } from "../src/workspaces/diagnostics.js";
|
|
import { WorkspaceRegistryError } from "../src/workspaces/git-repository.js";
|
|
import { WorkspaceRegistry, type WorkspaceRevision } from "../src/workspaces/registry.js";
|
|
import {
|
|
parseWorkspaceYaml, renderWorkspaceDocs, serializeWorkspaceYaml, validateCanonicalWorkspace,
|
|
type CanonicalWorkspace, type WorkspaceV2,
|
|
} from "../src/workspaces/schema.js";
|
|
|
|
const workspace: CanonicalWorkspace = {
|
|
workspace: {
|
|
schema_version: 3,
|
|
id: "psd-clinical",
|
|
name: "Policlinico San Donato",
|
|
description: "Clinical analytics workspace",
|
|
language: "it",
|
|
},
|
|
dwh: {
|
|
engine: "postgres",
|
|
database: "warehouse",
|
|
schema: "datawarehouse",
|
|
supported_transports: ["postgres_direct"],
|
|
},
|
|
semantic_index: {
|
|
vector_store: {
|
|
engine: "qdrant",
|
|
collection: "psd-clinical",
|
|
dimensions: 1024,
|
|
distance: "cosine",
|
|
},
|
|
embedding: {
|
|
provider: "ollama_internal",
|
|
model: "qwen3-embedding:0.6b",
|
|
dimensions: 1024,
|
|
},
|
|
},
|
|
llm_policy: { allowed: ["zai/glm-5.2"] },
|
|
};
|
|
|
|
const workspaceV2: WorkspaceV2 = {
|
|
workspace: {
|
|
schema_version: 2,
|
|
id: "psd-clinical",
|
|
name: "Policlinico San Donato",
|
|
description: "Clinical analytics workspace",
|
|
language: "it",
|
|
},
|
|
dwh: {
|
|
engine: "postgres",
|
|
database: "warehouse",
|
|
schema: "datawarehouse",
|
|
supported_transports: ["rest_api"],
|
|
},
|
|
semantic_index: {
|
|
vector_store: {
|
|
engine: "pgvector",
|
|
database: "warehouse",
|
|
schema: "vectors",
|
|
collection: "clinical_documents",
|
|
dimensions: 768,
|
|
distance: "cosine",
|
|
supported_transports: ["rest_api"],
|
|
},
|
|
embedding: {
|
|
provider: "ollama_compatible",
|
|
model: "nomic-embed-text-v2-moe",
|
|
dimensions: 768,
|
|
},
|
|
},
|
|
diagnostics: {
|
|
dwh_rest: {
|
|
method: "GET",
|
|
path: "/health",
|
|
auth: "none",
|
|
response: { database: "database", schema: "schema" },
|
|
},
|
|
vector_rest: {
|
|
metadata: {
|
|
method: "GET",
|
|
path: "/metadata",
|
|
auth: "none",
|
|
response: { collection: "collection", dimensions: "dimensions", distance: "distance" },
|
|
},
|
|
},
|
|
embedding: {
|
|
method: "GET",
|
|
path: "/models",
|
|
auth: "none",
|
|
response: { model: "model", dimensions: "dimensions" },
|
|
},
|
|
},
|
|
llm_policy: { allowed: ["zai/glm-5.2"] },
|
|
};
|
|
|
|
const revision: WorkspaceRevision = {
|
|
id: workspace.workspace.id,
|
|
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">;
|
|
|
|
function registryFake(overrides: Partial<RegistryFake> = {}): RegistryFake {
|
|
return {
|
|
bootstrap: vi.fn(async () => ({
|
|
branch: "main", head: revision.commit, ahead: 0, behind: 0, degraded: false,
|
|
})),
|
|
pull: vi.fn(async () => ({
|
|
branch: "main", head: revision.commit, ahead: 0, behind: 0, degraded: false,
|
|
})),
|
|
list: vi.fn(async () => [revision]),
|
|
read: vi.fn(async () => ({ workspace, revision })),
|
|
publish: vi.fn(async () => revision),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function appFor(registry: RegistryFake, diagnose = vi.fn(async () => ({ activatable: true, diagnostics: [] }))) {
|
|
return buildApp(loadConfig({
|
|
THT_HARNESS_DIR: "/missing-harness",
|
|
THT_WORKSPACE_REGISTRY_ROOT: "/tmp/thoth-route-test-registry",
|
|
}), {
|
|
thtRunner: {} as any,
|
|
workspaceRegistry: registry as WorkspaceRegistry,
|
|
workspaceDiagnoser: diagnose,
|
|
} as any);
|
|
}
|
|
|
|
function sha256(value: string | Buffer): string {
|
|
return createHash("sha256").update(value).digest("hex");
|
|
}
|
|
|
|
async function zip(files: Record<string, string>): Promise<Buffer> {
|
|
const archive = new yazl.ZipFile();
|
|
const chunks: Buffer[] = [];
|
|
archive.outputStream.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
for (const [name, contents] of Object.entries(files)) archive.addBuffer(Buffer.from(contents), name);
|
|
archive.end();
|
|
await once(archive.outputStream, "end");
|
|
return Buffer.concat(chunks);
|
|
}
|
|
|
|
async function validBundle(): Promise<Buffer> {
|
|
const workspaceYaml = serializeWorkspaceYaml(workspace);
|
|
const docs = renderWorkspaceDocs(workspace);
|
|
const contractEnv = docs.envExample;
|
|
const readme = docs.markdown;
|
|
return await zip({
|
|
"manifest.json": JSON.stringify({
|
|
schema_version: 1,
|
|
workspace_id: workspace.workspace.id,
|
|
files: {
|
|
"workspace.yaml": sha256(workspaceYaml),
|
|
"contract.env.example": sha256(contractEnv),
|
|
"README.md": sha256(readme),
|
|
},
|
|
}),
|
|
"workspace.yaml": workspaceYaml,
|
|
"contract.env.example": contractEnv,
|
|
"README.md": readme,
|
|
});
|
|
}
|
|
|
|
function zipWithZipSlipEntry(): Promise<Buffer> {
|
|
return zip({ "aa/escape.yaml": "bad" }).then((archive) => {
|
|
const safeName = Buffer.from("aa/escape.yaml");
|
|
const unsafeName = Buffer.from("../escape.yaml");
|
|
for (let offset = archive.indexOf(safeName); offset !== -1; offset = archive.indexOf(safeName, offset + safeName.length)) {
|
|
unsafeName.copy(archive, offset);
|
|
}
|
|
return archive;
|
|
});
|
|
}
|
|
|
|
async function importBundle(app: ReturnType<typeof appFor>, archive: Buffer) {
|
|
const boundary = "----thoth-workspace-test-boundary";
|
|
const payload = Buffer.concat([
|
|
Buffer.from(`--${boundary}\r\ncontent-disposition: form-data; name="bundle"; filename="workspace.zip"\r\ncontent-type: application/zip\r\n\r\n`),
|
|
archive,
|
|
Buffer.from(`\r\n--${boundary}--\r\n`),
|
|
]);
|
|
return await app.inject({
|
|
method: "POST",
|
|
url: "/workspaces/import",
|
|
headers: { "content-type": `multipart/form-data; boundary=${boundary}` },
|
|
payload,
|
|
});
|
|
}
|
|
|
|
test("returns a redacted registry status and pulls without Git credential details", async () => {
|
|
const registry = registryFake({
|
|
bootstrap: vi.fn(async () => ({
|
|
branch: "main", head: revision.commit, ahead: 0, behind: 0, degraded: true, lastError: "git_auth_failed" as const,
|
|
})),
|
|
});
|
|
const app = appFor(registry);
|
|
|
|
const status = await app.inject({ method: "GET", url: "/workspace-registry/status" });
|
|
const pull = await app.inject({ method: "POST", url: "/workspace-registry/pull" });
|
|
|
|
expect(status.statusCode).toBe(200);
|
|
expect(status.json()).toEqual({
|
|
branch: "main", head: revision.commit, ahead: 0, behind: 0, degraded: true, lastError: "git_auth_failed",
|
|
});
|
|
expect(pull.statusCode).toBe(200);
|
|
expect(JSON.stringify([status.json(), pull.json()])).not.toMatch(/token|password|ssh:\/\//i);
|
|
});
|
|
|
|
test("lists compatible workspace summaries and reads a validated workspace", async () => {
|
|
const app = appFor(registryFake());
|
|
|
|
const list = await app.inject({ method: "GET", url: "/workspaces" });
|
|
const detail = await app.inject({ method: "GET", url: "/workspaces/psd-clinical" });
|
|
|
|
expect(list.statusCode).toBe(200);
|
|
expect(list.json()).toEqual([expect.objectContaining({
|
|
id: "psd-clinical", name: "psd-clinical", file: "psd-clinical.yaml", displayName: "Policlinico San Donato",
|
|
})]);
|
|
expect(detail.statusCode).toBe(200);
|
|
expect(detail.json()).toMatchObject({ workspace, revision });
|
|
});
|
|
|
|
test("validates a canonical workspace and runs the injected installation diagnostic", async () => {
|
|
const diagnose = vi.fn(async () => ({
|
|
activatable: false,
|
|
diagnostics: [{ level: "error" as const, code: "binding_missing" as const, field: "THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE", message: "Installation binding is missing or invalid." }],
|
|
}));
|
|
const app = appFor(registryFake(), diagnose);
|
|
|
|
const validate = await app.inject({ method: "POST", url: "/workspaces/validate", payload: { workspace } });
|
|
|
|
expect(validate.statusCode).toBe(200);
|
|
expect(validate.json()).toMatchObject({ workspace });
|
|
expect(diagnose).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);
|
|
|
|
const testResult = await app.inject({ method: "POST", url: "/workspaces/psd-clinical/test", payload: {} });
|
|
|
|
expect(testResult.statusCode).toBe(200);
|
|
expect(testResult.json()).toMatchObject({ activatable: true, diagnostics: [] });
|
|
expect(diagnose).toHaveBeenCalledWith(workspace, expect.objectContaining({
|
|
vector: expect.objectContaining({ missing: [], values: {} }),
|
|
vectorWriter: expect.objectContaining({ missing: [], values: {} }),
|
|
embedding: expect.objectContaining({ missing: [], values: {} }),
|
|
}), { writeProbe: false });
|
|
});
|
|
|
|
test("reports missing Evidence binding through the real test route without changing registry revision", async () => {
|
|
const evidenceWorkspace: CanonicalWorkspace = {
|
|
...workspace,
|
|
evidence: {
|
|
source: {
|
|
type: "http",
|
|
uris: ["https://evidence.example.test/guide.md"],
|
|
authentication: "signed_urls_file",
|
|
connect_timeout_ms: 5_000,
|
|
read_timeout_ms: 30_000,
|
|
max_bytes: 10 * 1024 * 1024,
|
|
max_redirects: 5,
|
|
allow_private_hosts: false,
|
|
max_cache_bytes: 64 * 1024 * 1024,
|
|
},
|
|
policy: { max_chunk_chars: 4_000, retain_published_generations: 3 },
|
|
},
|
|
};
|
|
const read = vi.fn(async () => ({ workspace: evidenceWorkspace, revision }));
|
|
const registry = registryFake({ read });
|
|
const app = appFor(registry, createProductionWorkspaceDiagnoser(100));
|
|
const variable = "THT_WS_PSD_CLINICAL_EVIDENCE_SIGNED_URLS_FILE";
|
|
const previous = process.env[variable];
|
|
delete process.env[variable];
|
|
|
|
try {
|
|
const res = await app.inject({ method: "POST", url: "/workspaces/psd-clinical/test", payload: {} });
|
|
|
|
expect(res.statusCode).toBe(200);
|
|
const body = res.json();
|
|
expect(body.activatable).toBe(false);
|
|
expect(body.diagnostics).toEqual(expect.arrayContaining([expect.objectContaining({
|
|
code: "binding_missing",
|
|
field: "evidence.source.authentication",
|
|
variable,
|
|
})]));
|
|
expect(read).toHaveBeenCalledTimes(1);
|
|
expect(registry.publish).not.toHaveBeenCalled();
|
|
expect(revision).toMatchObject({ commit: "a".repeat(40), blob: "b".repeat(40) });
|
|
} finally {
|
|
if (previous === undefined) delete process.env[variable];
|
|
else process.env[variable] = previous;
|
|
}
|
|
});
|
|
|
|
test("returns a 409 field conflict instead of overwriting a changed workspace", async () => {
|
|
const conflict = Object.assign(
|
|
new WorkspaceRegistryError("workspace_conflict", "Workspace has changed"),
|
|
{
|
|
fields: ["semantic_index.embedding.model"],
|
|
expected: { commit: "c".repeat(40), blob: "d".repeat(40) },
|
|
actual: { commit: revision.commit, blob: revision.blob },
|
|
base: workspace,
|
|
local: { ...workspace, semantic_index: { ...workspace.semantic_index, embedding: { ...workspace.semantic_index.embedding, model: "local/model" } } },
|
|
remote: { ...workspace, semantic_index: { ...workspace.semantic_index, embedding: { ...workspace.semantic_index.embedding, model: "remote/model" } } },
|
|
},
|
|
);
|
|
const registry = registryFake({ publish: vi.fn(async () => { throw conflict; }) });
|
|
const app = appFor(registry);
|
|
const staleUpdate = {
|
|
action: "update",
|
|
workspace,
|
|
baseCommit: "c".repeat(40),
|
|
baseBlob: "d".repeat(40),
|
|
};
|
|
|
|
const res = await app.inject({ method: "POST", url: "/workspaces/publish", payload: staleUpdate });
|
|
|
|
expect(res.statusCode).toBe(409);
|
|
expect(res.json()).toMatchObject({
|
|
code: "workspace_conflict",
|
|
fields: ["semantic_index.embedding.model"],
|
|
expected: { commit: "c".repeat(40), blob: "d".repeat(40) },
|
|
actual: { commit: revision.commit, blob: revision.blob },
|
|
base: workspace,
|
|
remote: expect.objectContaining({
|
|
semantic_index: expect.objectContaining({
|
|
embedding: expect.objectContaining({ model: "remote/model" }),
|
|
}),
|
|
}),
|
|
});
|
|
});
|
|
|
|
test("maps a stale registry commit to HTTP 409 without conflict payloads", async () => {
|
|
const registry = registryFake({
|
|
publish: vi.fn(async () => {
|
|
throw new WorkspaceRegistryError("workspace_stale", "Workspace revision is stale");
|
|
}),
|
|
});
|
|
const app = appFor(registry);
|
|
|
|
const res = await app.inject({ method: "POST", url: "/workspaces/publish", payload: {
|
|
action: "update",
|
|
workspace,
|
|
baseCommit: "c".repeat(40),
|
|
baseBlob: "d".repeat(40),
|
|
} });
|
|
|
|
expect(res.statusCode).toBe(409);
|
|
expect(res.json()).toEqual({ code: "workspace_stale", message: "Workspace revision is stale." });
|
|
});
|
|
|
|
test("exports generated public artifacts without secret values", async () => {
|
|
const app = appFor(registryFake());
|
|
|
|
const res = await app.inject({ method: "GET", url: "/workspaces/psd-clinical/export" });
|
|
|
|
expect(res.statusCode).toBe(200);
|
|
expect(res.headers["content-disposition"]).toMatch(/attachment; filename="psd-clinical\.zip"/);
|
|
expect(res.headers["content-type"]).toMatch(/application\/zip/);
|
|
expect(res.rawPayload.toString("utf8")).toContain("contract.env.example");
|
|
expect(res.rawPayload.toString("utf8")).not.toContain("secret-value");
|
|
});
|
|
|
|
test("rejects a zip-slip import without publishing or writing a checkout file", async () => {
|
|
const registry = registryFake();
|
|
const app = appFor(registry);
|
|
|
|
const res = await importBundle(app, await zipWithZipSlipEntry());
|
|
|
|
expect(res.statusCode).toBe(400);
|
|
expect(res.json()).toMatchObject({ code: "workspace_invalid" });
|
|
expect(registry.publish).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("imports an exact generated bundle only as a browser draft", async () => {
|
|
const registry = registryFake();
|
|
const app = appFor(registry);
|
|
|
|
const res = await importBundle(app, await validBundle());
|
|
|
|
expect(res.statusCode).toBe(200);
|
|
expect(res.json()).toMatchObject({ draft: { workspace } });
|
|
expect(registry.publish).not.toHaveBeenCalled();
|
|
});
|
|
|
|
|
|
const runFile = promisify(execFile);
|
|
const realRouteRoots: string[] = [];
|
|
|
|
interface RealRouteFixture {
|
|
root: string;
|
|
remote: string;
|
|
author: string;
|
|
registryRoot: string;
|
|
initialCommit: string;
|
|
app: ReturnType<typeof buildApp>;
|
|
registry: WorkspaceRegistry;
|
|
}
|
|
|
|
const EVIDENCE_FILE_BYTES = "PUBLIC-EVIDENCE-FILE-BYTES-NOT-FOR-ZIP\n";
|
|
const SECRET_CANARY = "CANARY-EVIDENCE-ROUTE-SECRET-DO-NOT-LEAK";
|
|
|
|
function withEvidence(
|
|
source: Partial<CanonicalWorkspace["evidence"]["source"]> & { type: "filesystem" | "http" | "s3" },
|
|
changes: Partial<CanonicalWorkspace["evidence"]["policy"]> = {},
|
|
): CanonicalWorkspace {
|
|
return validateCanonicalWorkspace({
|
|
...workspace,
|
|
evidence: { source, policy: changes },
|
|
});
|
|
}
|
|
|
|
const filesystemEvidenceWorkspace = withEvidence({
|
|
type: "filesystem", uri: "workspace-content/psd-clinical/evidence",
|
|
});
|
|
const httpEvidenceWorkspace = withEvidence({
|
|
type: "http",
|
|
uris: ["https://evidence.example.test/guide.md"],
|
|
authentication: "signed_urls_file",
|
|
});
|
|
|
|
async function realGit(cwd: string, args: string[]): Promise<string> {
|
|
return (await runFile("git", args, { cwd })).stdout.trim();
|
|
}
|
|
|
|
async function createRealRouteFixture(
|
|
initialWorkspace: CanonicalWorkspace = filesystemEvidenceWorkspace,
|
|
): Promise<RealRouteFixture> {
|
|
const root = mkdtempSync(join(tmpdir(), "thoth-real-workspace-route-"));
|
|
realRouteRoots.push(root);
|
|
const remote = join(root, "remote.git");
|
|
const author = join(root, "author");
|
|
const registryRoot = join(root, "registry");
|
|
await realGit(root, ["init", "--bare", "--initial-branch=main", remote]);
|
|
mkdirSync(author);
|
|
await realGit(author, ["init", "--initial-branch=main"]);
|
|
await realGit(author, ["config", "user.name", "Workspace Route Test"]);
|
|
await realGit(author, ["config", "user.email", "workspace-route@example.invalid"]);
|
|
mkdirSync(join(author, "workspaces"));
|
|
writeFileSync(join(author, "workspaces", "psd-clinical.yaml"), serializeWorkspaceYaml(initialWorkspace));
|
|
if (initialWorkspace.evidence?.source.type === "filesystem") {
|
|
mkdirSync(join(author, "workspace-content", "psd-clinical", "evidence"), { recursive: true });
|
|
writeFileSync(
|
|
join(author, "workspace-content", "psd-clinical", "evidence", "guide.md"),
|
|
EVIDENCE_FILE_BYTES,
|
|
);
|
|
}
|
|
await realGit(author, ["add", "."]);
|
|
await realGit(author, ["commit", "-m", "Initial Evidence workspace"]);
|
|
await realGit(author, ["remote", "add", "origin", remote]);
|
|
await realGit(author, ["push", "origin", "main"]);
|
|
const initialCommit = await realGit(author, ["rev-parse", "HEAD"]);
|
|
const config = loadConfig({
|
|
THT_HARNESS_DIR: "/missing-harness",
|
|
THT_WORKSPACE_REGISTRY_ROOT: registryRoot,
|
|
THT_WORKSPACE_GIT_REMOTE: remote,
|
|
THT_WORKSPACE_GIT_AUTHOR_NAME: "Workspace Route Publisher",
|
|
THT_WORKSPACE_GIT_AUTHOR_EMAIL: "workspace-route-publisher@example.invalid",
|
|
});
|
|
const registry = new WorkspaceRegistry(config.workspaceRegistry);
|
|
const app = buildApp(config, {
|
|
thtRunner: {} as any,
|
|
workspaceRegistry: registry,
|
|
workspaceDiagnoser: vi.fn(async () => ({ activatable: true, diagnostics: [] })),
|
|
});
|
|
return { root, remote, author, registryRoot, initialCommit, app, registry };
|
|
}
|
|
|
|
async function extractZip(source: Buffer): Promise<Record<string, Buffer>> {
|
|
return await new Promise((resolve, reject) => {
|
|
yauzl.fromBuffer(source, { lazyEntries: true, strictFileNames: true }, (error, archive) => {
|
|
if (error || !archive) return reject(error ?? new Error("archive unavailable"));
|
|
const files: Record<string, Buffer> = {};
|
|
archive.on("error", reject);
|
|
archive.on("entry", (entry) => {
|
|
if (entry.fileName.startsWith("/") || entry.fileName.includes("..") || entry.fileName.includes("\\")) {
|
|
archive.close();
|
|
reject(new Error("unsafe exported path"));
|
|
return;
|
|
}
|
|
archive.openReadStream(entry, (streamError, stream) => {
|
|
if (streamError || !stream) return reject(streamError ?? new Error("entry unavailable"));
|
|
const chunks: Buffer[] = [];
|
|
stream.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
stream.on("error", reject);
|
|
stream.on("end", () => {
|
|
files[entry.fileName] = Buffer.concat(chunks);
|
|
archive.readEntry();
|
|
});
|
|
});
|
|
});
|
|
archive.on("end", () => resolve(files));
|
|
archive.readEntry();
|
|
});
|
|
});
|
|
}
|
|
|
|
afterEach(() => {
|
|
realRouteRoots.splice(0).forEach((root) => rmSync(root, { recursive: true, force: true }));
|
|
});
|
|
|
|
test.each([
|
|
{
|
|
source: { type: "filesystem", uri: "workspace-content/psd-clinical/evidence" },
|
|
expectedVariables: [],
|
|
},
|
|
{
|
|
source: {
|
|
type: "http", uris: ["https://evidence.example.test/guide.md"],
|
|
authentication: "signed_urls_file",
|
|
},
|
|
expectedVariables: ["THT_WS_PSD_CLINICAL_EVIDENCE_SIGNED_URLS_FILE"],
|
|
},
|
|
{
|
|
source: { type: "s3", uri: "s3://clinical-evidence/published/", credentials: "static_files" },
|
|
expectedVariables: [
|
|
"THT_WS_PSD_CLINICAL_EVIDENCE_ACCESS_KEY_FILE",
|
|
"THT_WS_PSD_CLINICAL_EVIDENCE_SECRET_KEY_FILE",
|
|
"THT_WS_PSD_CLINICAL_EVIDENCE_SESSION_TOKEN_FILE",
|
|
],
|
|
},
|
|
])("real validate route canonicalizes $source.type Evidence and returns only its file contract", async ({
|
|
source, expectedVariables,
|
|
}) => {
|
|
const fixture = await createRealRouteFixture();
|
|
const response = await fixture.app.inject({
|
|
method: "POST", url: "/workspaces/validate",
|
|
payload: { workspace: { ...workspace, evidence: { source } } },
|
|
});
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
const body = response.json();
|
|
expect(body.workspace.evidence.policy).toEqual({
|
|
max_chunk_chars: 4_000, retain_published_generations: 3,
|
|
});
|
|
expect(body.workspace.evidence.source.max_bytes).toBe(10 * 1024 * 1024);
|
|
expect(body.contract.variables.filter(({ role }: { role: string }) => role === "EVIDENCE")
|
|
.map(({ name }: { name: string }) => name)).toEqual(expectedVariables);
|
|
});
|
|
|
|
test("real publish create/update, pull, list, and read preserve a complete Evidence descriptor", async () => {
|
|
const fixture = await createRealRouteFixture(httpEvidenceWorkspace);
|
|
const status = await fixture.app.inject({ method: "GET", url: "/workspace-registry/status" });
|
|
const created = validateCanonicalWorkspace({
|
|
...httpEvidenceWorkspace,
|
|
workspace: { ...httpEvidenceWorkspace.workspace, id: "research-clinical", name: "Research Clinical" },
|
|
semantic_index: {
|
|
...httpEvidenceWorkspace.semantic_index,
|
|
vector_store: { ...httpEvidenceWorkspace.semantic_index.vector_store, collection: "research-clinical" },
|
|
},
|
|
});
|
|
const create = await fixture.app.inject({
|
|
method: "POST", url: "/workspaces/publish",
|
|
payload: { action: "create", workspace: created, baseCommit: status.json().head },
|
|
});
|
|
const createdRevision = create.json().revision as WorkspaceRevision;
|
|
const updated = validateCanonicalWorkspace({
|
|
...created,
|
|
evidence: {
|
|
...created.evidence,
|
|
policy: { max_chunk_chars: 8_192, retain_published_generations: 7 },
|
|
},
|
|
});
|
|
|
|
const update = await fixture.app.inject({
|
|
method: "POST", url: "/workspaces/publish",
|
|
payload: {
|
|
action: "update", workspace: updated,
|
|
baseCommit: createdRevision.commit, baseBlob: createdRevision.blob,
|
|
},
|
|
});
|
|
expect(update.statusCode).toBe(200);
|
|
await realGit(fixture.author, ["pull", "--ff-only", "origin", "main"]);
|
|
const remotelyEdited = validateCanonicalWorkspace({
|
|
...updated,
|
|
evidence: {
|
|
...updated.evidence,
|
|
policy: { max_chunk_chars: 9_001, retain_published_generations: 9 },
|
|
},
|
|
});
|
|
writeFileSync(
|
|
join(fixture.author, "workspaces", "research-clinical.yaml"),
|
|
serializeWorkspaceYaml(remotelyEdited),
|
|
);
|
|
await realGit(fixture.author, ["add", "workspaces/research-clinical.yaml"]);
|
|
await realGit(fixture.author, ["commit", "-m", "Remote Evidence-only descriptor edit"]);
|
|
await realGit(fixture.author, ["push", "origin", "main"]);
|
|
const remoteCommit = await realGit(fixture.author, ["rev-parse", "HEAD"]);
|
|
|
|
const pull = await fixture.app.inject({ method: "POST", url: "/workspace-registry/pull" });
|
|
const list = await fixture.app.inject({ method: "GET", url: "/workspaces" });
|
|
const read = await fixture.app.inject({ method: "GET", url: "/workspaces/research-clinical" });
|
|
|
|
expect(status.statusCode).toBe(200);
|
|
expect(create.statusCode).toBe(200);
|
|
expect(update.statusCode).toBe(200);
|
|
expect(pull.statusCode).toBe(200);
|
|
expect(pull.json().head).toBe(remoteCommit);
|
|
expect(list.statusCode).toBe(200);
|
|
expect(list.json().find(({ id }: { id: string }) => id === "research-clinical").workspace)
|
|
.toEqual(remotelyEdited);
|
|
expect(read.statusCode).toBe(200);
|
|
expect(read.json().workspace).toEqual(remotelyEdited);
|
|
});
|
|
|
|
test("real route reports a safe field for an Evidence-only concurrent edit", async () => {
|
|
const fixture = await createRealRouteFixture(httpEvidenceWorkspace);
|
|
await fixture.registry.bootstrap();
|
|
const base = await fixture.registry.read("psd-clinical");
|
|
const remote = withEvidence(
|
|
{ ...httpEvidenceWorkspace.evidence!.source },
|
|
{ max_chunk_chars: 9_000, retain_published_generations: 3 },
|
|
);
|
|
writeFileSync(join(fixture.author, "workspaces", "psd-clinical.yaml"), serializeWorkspaceYaml(remote));
|
|
await realGit(fixture.author, ["add", "workspaces/psd-clinical.yaml"]);
|
|
await realGit(fixture.author, ["commit", "-m", "Change Evidence policy only"]);
|
|
await realGit(fixture.author, ["push", "origin", "main"]);
|
|
const local = withEvidence(
|
|
{ ...httpEvidenceWorkspace.evidence!.source },
|
|
{ max_chunk_chars: 4_000, retain_published_generations: 8 },
|
|
);
|
|
|
|
const response = await fixture.app.inject({
|
|
method: "POST", url: "/workspaces/publish",
|
|
payload: {
|
|
action: "update", workspace: local,
|
|
baseCommit: base.revision.commit, baseBlob: base.revision.blob,
|
|
},
|
|
});
|
|
|
|
expect(response.statusCode).toBe(409);
|
|
expect(response.json()).toMatchObject({
|
|
code: "workspace_conflict", fields: ["evidence.policy.max_chunk_chars"],
|
|
});
|
|
expect(response.body).not.toContain(SECRET_CANARY);
|
|
});
|
|
|
|
test.each([
|
|
["absolute", "/tmp/CANARY-EVIDENCE-ROUTE-SECRET-DO-NOT-LEAK"],
|
|
["traversal", "workspace-content/psd-clinical/../CANARY-EVIDENCE-ROUTE-SECRET-DO-NOT-LEAK"],
|
|
["cross-workspace", "workspace-content/research/evidence"],
|
|
])("real publish rejects %s filesystem Evidence paths without changing HEAD", async (_label, uri) => {
|
|
const fixture = await createRealRouteFixture();
|
|
await fixture.registry.bootstrap();
|
|
const base = await fixture.registry.read("psd-clinical");
|
|
const invalid = structuredClone(filesystemEvidenceWorkspace) as any;
|
|
invalid.evidence.source.uri = uri;
|
|
|
|
const response = await fixture.app.inject({
|
|
method: "POST", url: "/workspaces/publish",
|
|
payload: { action: "update", workspace: invalid, baseCommit: base.revision.commit, baseBlob: base.revision.blob },
|
|
});
|
|
|
|
expect(response.statusCode).toBe(400);
|
|
expect(response.json()).toEqual({ code: "workspace_invalid", message: "Workspace request or bundle is invalid." });
|
|
expect(response.body).not.toContain(SECRET_CANARY);
|
|
expect(await realGit(fixture.author, ["--git-dir", fixture.remote, "rev-parse", "HEAD"]))
|
|
.toBe(fixture.initialCommit);
|
|
});
|
|
|
|
test.each([
|
|
{
|
|
label: "credential-bearing HTTP URI",
|
|
source: { type: "http", uris: [`https://user:${SECRET_CANARY}@evidence.example.test/guide.md`] },
|
|
},
|
|
{
|
|
label: "unsupported HTTP protocol",
|
|
source: { type: "http", uris: [`ftp://evidence.example.test/${SECRET_CANARY}`] },
|
|
},
|
|
{
|
|
label: "inline S3 credential field",
|
|
source: { type: "s3", uri: "s3://clinical-evidence/published/", access_key: SECRET_CANARY },
|
|
},
|
|
])("real publish rejects $label without echoing it or changing HEAD", async ({ source }) => {
|
|
const fixture = await createRealRouteFixture();
|
|
await fixture.registry.bootstrap();
|
|
const base = await fixture.registry.read("psd-clinical");
|
|
const invalid = structuredClone(base.workspace) as any;
|
|
invalid.evidence = { source };
|
|
const response = await fixture.app.inject({
|
|
method: "POST", url: "/workspaces/publish",
|
|
payload: {
|
|
action: "update", workspace: invalid,
|
|
baseCommit: base.revision.commit, baseBlob: base.revision.blob,
|
|
},
|
|
});
|
|
|
|
expect(response.statusCode).toBe(400);
|
|
expect(response.json()).toEqual({ code: "workspace_invalid", message: "Workspace request or bundle is invalid." });
|
|
expect(response.body).not.toContain(SECRET_CANARY);
|
|
expect(await realGit(fixture.author, ["--git-dir", fixture.remote, "rev-parse", "HEAD"]))
|
|
.toBe(fixture.initialCommit);
|
|
});
|
|
|
|
test("real publish and pull fail safely when the contextual Evidence Git tree is missing", async () => {
|
|
const fixture = await createRealRouteFixture();
|
|
await fixture.registry.bootstrap();
|
|
const current = await fixture.registry.read("psd-clinical");
|
|
const missing = validateCanonicalWorkspace({
|
|
...workspace,
|
|
workspace: { ...workspace.workspace, id: "missing-evidence", name: "Missing Evidence" },
|
|
semantic_index: {
|
|
...workspace.semantic_index,
|
|
vector_store: { ...workspace.semantic_index.vector_store, collection: "missing-evidence" },
|
|
},
|
|
evidence: { source: { type: "filesystem", uri: "workspace-content/missing-evidence/evidence" } },
|
|
});
|
|
const publish = await fixture.app.inject({
|
|
method: "POST", url: "/workspaces/publish",
|
|
payload: { action: "create", workspace: missing, baseCommit: current.revision.commit },
|
|
});
|
|
expect(publish.statusCode).toBe(400);
|
|
expect(publish.json()).toEqual({ code: "workspace_invalid", message: "Workspace request or bundle is invalid." });
|
|
expect(await realGit(fixture.author, ["--git-dir", fixture.remote, "rev-parse", "HEAD"]))
|
|
.toBe(fixture.initialCommit);
|
|
|
|
rmSync(join(fixture.author, "workspace-content", "psd-clinical", "evidence"), { recursive: true });
|
|
await realGit(fixture.author, ["add", "-A"]);
|
|
await realGit(fixture.author, ["commit", "-m", "Remove Evidence tree"]);
|
|
await realGit(fixture.author, ["push", "origin", "main"]);
|
|
const pull = await fixture.app.inject({ method: "POST", url: "/workspace-registry/pull" });
|
|
expect(pull.statusCode).toBe(400);
|
|
expect(pull.json()).toEqual({ code: "workspace_invalid", message: "Workspace request or bundle is invalid." });
|
|
expect(pull.body).not.toContain(SECRET_CANARY);
|
|
await expect(fixture.registry.read("psd-clinical")).resolves.toMatchObject({
|
|
revision: { commit: fixture.initialCommit },
|
|
});
|
|
});
|
|
|
|
test("real export and import preserve stable public Evidence artifacts without Evidence or secret bytes", async () => {
|
|
const fixture = await createRealRouteFixture();
|
|
const secretDirectory = join(fixture.root, "fixture-secrets");
|
|
mkdirSync(secretDirectory);
|
|
writeFileSync(join(secretDirectory, "credential"), SECRET_CANARY);
|
|
await fixture.registry.bootstrap();
|
|
|
|
const firstResponse = await fixture.app.inject({ method: "GET", url: "/workspaces/psd-clinical/export" });
|
|
const secondResponse = await fixture.app.inject({ method: "GET", url: "/workspaces/psd-clinical/export" });
|
|
expect(firstResponse.statusCode).toBe(200);
|
|
expect(secondResponse.statusCode).toBe(200);
|
|
const first = await extractZip(firstResponse.rawPayload);
|
|
const second = await extractZip(secondResponse.rawPayload);
|
|
const names = ["manifest.json", "workspace.yaml", "contract.env.example", "README.md"];
|
|
expect(Object.keys(first).sort()).toEqual([...names].sort());
|
|
expect(Object.keys(second).sort()).toEqual([...names].sort());
|
|
for (const name of names) expect(second[name]).toEqual(first[name]);
|
|
|
|
const descriptor = parseWorkspaceYaml(first["workspace.yaml"].toString("utf8"));
|
|
const docs = renderWorkspaceDocs(descriptor);
|
|
const manifest = JSON.parse(first["manifest.json"].toString("utf8"));
|
|
expect(descriptor).toEqual(filesystemEvidenceWorkspace);
|
|
expect(first["contract.env.example"].toString("utf8")).toBe(docs.envExample);
|
|
expect(first["README.md"].toString("utf8")).toBe(docs.markdown);
|
|
expect(manifest.files).toEqual({
|
|
"workspace.yaml": sha256(first["workspace.yaml"]),
|
|
"contract.env.example": sha256(first["contract.env.example"]),
|
|
"README.md": sha256(first["README.md"]),
|
|
});
|
|
const publicBytes = Buffer.concat(Object.values(first)).toString("utf8");
|
|
expect(publicBytes).not.toContain(EVIDENCE_FILE_BYTES.trim());
|
|
expect(publicBytes).not.toContain(SECRET_CANARY);
|
|
|
|
const imported = await importBundle(fixture.app, firstResponse.rawPayload);
|
|
expect(imported.statusCode).toBe(200);
|
|
expect(imported.json().draft.workspace).toEqual(filesystemEvidenceWorkspace);
|
|
expect(imported.json().draft.contract.variables.some(({ role }: { role: string }) => role === "EVIDENCE"))
|
|
.toBe(false);
|
|
expect(imported.body).not.toContain(EVIDENCE_FILE_BYTES.trim());
|
|
expect(imported.body).not.toContain(SECRET_CANARY);
|
|
});
|