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, validateWorkspaceDescriptor, type CanonicalWorkspace, } 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 = { 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 workspaceV1 = { ...workspaceV2, workspace: { ...workspaceV2.workspace, schema_version: 1 as const }, }; const revision: WorkspaceRevision = { id: workspace.workspace.id, commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/registry/snapshots/psd-clinical.yaml", }; type RegistryFake = Pick; function registryFake(overrides: Partial = {}): 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]), listCatalog: vi.fn(async () => [{ id: "psd-clinical", name: "Policlinico San Donato", configurationState: "ready", 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): Promise { 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 { 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 { 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, 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/workspace.yaml", displayName: "Policlinico San Donato", configurationState: "ready", })]); expect(detail.statusCode).toBe(200); expect(detail.json()).toMatchObject({ workspace, revision }); expect(list.json()[0].revision).not.toHaveProperty("state"); expect(detail.json().revision).not.toHaveProperty("state"); }); 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.each([ ["v1", workspaceV1], ["v2", workspaceV2], ])("rejects schema %s at validate and publish boundaries with a sanitized error", async (_version, legacy) => { const registry = registryFake(); const app = appFor(registry); for (const request of [ { url: "/workspaces/validate", payload: { workspace: legacy } }, { url: "/workspaces/publish", payload: { action: "create", workspace: legacy, baseCommit: revision.commit }, }, ]) { const response = await app.inject({ method: "POST", ...request }); expect(response.statusCode).toBe(400); expect(response.json()).toEqual({ code: "workspace_invalid", message: "Workspace request or bundle is invalid.", }); expect(response.body).not.toMatch(/migration_required|schema version/i); } expect(registry.publish).not.toHaveBeenCalled(); }); 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, { dwh: expect.objectContaining({ transport: "postgres_direct" }), evidence: { 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: ["workspace.description"], expected: { commit: "c".repeat(40), blob: "d".repeat(40) }, actual: { commit: revision.commit, blob: revision.blob }, base: workspace, local: { ...workspace, workspace: { ...workspace.workspace, description: "Local description" } }, remote: { ...workspace, workspace: { ...workspace.workspace, description: "Remote description" } }, }, ); 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: ["workspace.description"], expected: { commit: "c".repeat(40), blob: "d".repeat(40) }, actual: { commit: revision.commit, blob: revision.blob }, base: workspace, remote: expect.objectContaining({ workspace: expect.objectContaining({ description: "Remote description" }), }), }); }); 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; 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 & { type: "filesystem" | "http" | "s3" }, changes: Partial = {}, ): CanonicalWorkspace { return validateWorkspaceDescriptor({ ...workspace, evidence: { source, policy: changes }, }); } const filesystemEvidenceWorkspace = withEvidence({ type: "filesystem", uri: "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 { return (await runFile("git", args, { cwd })).stdout.trim(); } async function createRealRouteFixture( initialWorkspace: CanonicalWorkspace = filesystemEvidenceWorkspace, ): Promise { 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"]); const catalogName = initialWorkspace.workspace.name; const catalogDescription = initialWorkspace.workspace.description; writeFileSync(join(author, "thoth-workspaces.yaml"), [ "schema_version: 1", "workspaces:", ` - id: psd-clinical\n name: ${catalogName}${catalogDescription ? `\n description: ${catalogDescription}` : ""}`, ` - id: research-clinical\n name: Research Clinical${catalogDescription ? `\n description: ${catalogDescription}` : ""}`, ` - id: missing-evidence\n name: Missing Evidence${catalogDescription ? `\n description: ${catalogDescription}` : ""}`, ].join("\n") + "\n"); mkdirSync(join(author, "psd-clinical"), { recursive: true }); writeFileSync(join(author, "psd-clinical", "workspace.yaml"), serializeWorkspaceYaml(initialWorkspace)); if (initialWorkspace.evidence?.source.type === "filesystem") { mkdirSync(join(author, "psd-clinical", "evidence"), { recursive: true }); writeFileSync( join(author, "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> { 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 = {}; 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: "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 bootstrap create, curator push/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 = validateWorkspaceDescriptor({ ...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 }, }); expect(create.statusCode).toBe(200); const createdRevision = create.json().revision as WorkspaceRevision; await realGit(fixture.author, ["pull", "--ff-only", "origin", "main"]); const remotelyEdited = validateWorkspaceDescriptor({ ...created, evidence: { ...created.evidence, policy: { max_chunk_chars: 9_001, retain_published_generations: 9 }, }, }); writeFileSync( join(fixture.author, "research-clinical", "workspace.yaml"), serializeWorkspaceYaml(remotelyEdited), ); await realGit(fixture.author, ["add", "research-clinical/workspace.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 update = await fixture.app.inject({ method: "POST", url: "/workspaces/publish", payload: { action: "update", workspace: remotelyEdited, baseCommit: createdRevision.commit, baseBlob: createdRevision.blob, }, }); expect(update.statusCode).toBe(409); expect(update.json()).toMatchObject({ code: "workspace_curator_owned" }); 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(pull.statusCode).toBe(200); // Explicit pull may produce a deterministic docs-only follow-up commit on top of the curator // commit; the active descriptor must always be the curator's bytes. const pulledHead = pull.json().head; const pulledDiff = pulledHead === remoteCommit ? [] : (await realGit(join(fixture.registryRoot, "repo"), ["diff", "--name-only", `${remoteCommit}..${pulledHead}`])).split(/\s+/).filter(Boolean); expect(pulledHead).toMatch(/^[0-9a-f]{40}$/); expect(pulledDiff.every((path) => path.startsWith("workspace-docs/"))).toBe(true); expect(list.statusCode).toBe(200); const summary = list.json().find(({ id }: { id: string }) => id === "research-clinical"); expect(summary.configurationState).toBe("ready"); expect(summary.revision.commit).toBe(pulledHead); expect(read.statusCode).toBe(200); expect(read.json().workspace).toEqual(remotelyEdited); }); test("real route refuses curator-owned updates with a safe 409 after 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, "psd-clinical", "workspace.yaml"), serializeWorkspaceYaml(remote)); await realGit(fixture.author, ["add", "psd-clinical/workspace.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_curator_owned" }); expect(response.body).not.toContain(SECRET_CANARY); }); test.each([ ["absolute", "/tmp/CANARY-EVIDENCE-ROUTE-SECRET-DO-NOT-LEAK"], ["traversal", "psd-clinical/../CANARY-EVIDENCE-ROUTE-SECRET-DO-NOT-LEAK"], ["cross-workspace", "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 = validateWorkspaceDescriptor({ ...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: "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(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, "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); });