import { chmodSync, mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, expect, test } from "vitest"; import { resolveBinding, resolveEvidenceBinding, resolveRuntimeBindings, supportsSessionRuntime, } from "../src/workspaces/bindings.js"; import { parseWorkspaceYaml } from "../src/workspaces/schema.js"; const workspaceV3 = parseWorkspaceYaml(`workspace: schema_version: 3 id: psd-clinical name: Policlinico San Donato language: it dwh: engine: postgres database: postgres schema: datawarehouse supported_transports: [postgres_direct, rest_api, ssh_tunnel] 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 temporaryRoots: string[] = []; afterEach(() => { temporaryRoots.splice(0).forEach((root) => rmSync(root, { recursive: true, force: true })); }); function secretPath(name: string): { root: string; path: string } { const root = mkdtempSync(join(tmpdir(), "thoth-binding-")); temporaryRoots.push(root); const secrets = join(root, "secrets"); mkdirSync(secrets); const path = join(secrets, name); writeFileSync(path, ""); return { root: secrets, path }; } test("resolves schema-v3 direct DWH bindings from the stable namespace", () => { const password = secretPath("dwh-password"); const result = resolveBinding(workspaceV3, "DWH", { THT_WS_PSD_CLINICAL_DWH_TRANSPORT: "postgres_direct", THT_WS_PSD_CLINICAL_DWH_HOST: "dwh.internal", THT_WS_PSD_CLINICAL_DWH_PORT: "5432", THT_WS_PSD_CLINICAL_DWH_USER: "reader", THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE: password.path, }, [password.root]); expect(result).toMatchObject({ transport: "postgres_direct", missing: [], values: { THT_WS_PSD_CLINICAL_DWH_HOST: "dwh.internal", THT_WS_PSD_CLINICAL_DWH_PORT: "5432", THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE: realpathSync(password.path), }, }); }); test("requires schema-v3 REST credentials unless the DWH diagnostic declares auth none", () => { expect(resolveBinding(workspaceV3, "DWH", { THT_WS_PSD_CLINICAL_DWH_TRANSPORT: "rest_api", THT_WS_PSD_CLINICAL_DWH_BASE_URL: "https://dwh.example.test", }, []).missing).toContain("THT_WS_PSD_CLINICAL_DWH_API_KEY_FILE"); const noAuth = parseWorkspaceYaml(`workspace: schema_version: 3 id: psd-clinical name: No auth language: en dwh: engine: postgres database: postgres schema: public supported_transports: [rest_api] semantic_index: vector_store: { engine: qdrant, collection: psd-clinical, dimensions: 1024, distance: cosine } embedding: { provider: ollama_internal, model: qwen3-embedding:0.6b, dimensions: 1024 } diagnostics: dwh_rest: method: GET path: /health auth: none response: { database: database, schema: schema } llm_policy: { allowed: [zai/glm-5.2] } `); expect(resolveBinding(noAuth, "DWH", { THT_WS_PSD_CLINICAL_DWH_TRANSPORT: "rest_api", THT_WS_PSD_CLINICAL_DWH_BASE_URL: "https://dwh.example.test", }, []).missing).toEqual([]); }); test("rejects unsupported transports and secret paths outside configured roots", () => { const outside = secretPath("outside-password"); const allowed = secretPath("allowed-password"); const directOnly = parseWorkspaceYaml(`workspace: schema_version: 3 id: psd-clinical name: Direct only language: en dwh: engine: postgres database: postgres schema: public 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] } `); expect(resolveBinding(directOnly, "DWH", { THT_WS_PSD_CLINICAL_DWH_TRANSPORT: "rest_api", }, []).missing).toContain("THT_WS_PSD_CLINICAL_DWH_TRANSPORT"); const result = resolveBinding(workspaceV3, "DWH", { THT_WS_PSD_CLINICAL_DWH_TRANSPORT: "postgres_direct", THT_WS_PSD_CLINICAL_DWH_HOST: "dwh.internal", THT_WS_PSD_CLINICAL_DWH_PORT: "5432", THT_WS_PSD_CLINICAL_DWH_USER: "reader", THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE: outside.path, }, [allowed.root]); expect(result.missing).toContain("THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE"); expect(JSON.stringify(result)).not.toContain(outside.path); }); test("runtime bindings contain only DWH and Evidence roles", () => { const password = secretPath("dwh-password"); const bindings = resolveRuntimeBindings(workspaceV3, { THT_WS_PSD_CLINICAL_DWH_TRANSPORT: "postgres_direct", THT_WS_PSD_CLINICAL_DWH_HOST: "dwh.internal", THT_WS_PSD_CLINICAL_DWH_PORT: "5432", THT_WS_PSD_CLINICAL_DWH_USER: "reader", THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE: password.path, THT_WS_PSD_CLINICAL_VECTOR_BASE_URL: "https://ignored.example.test", }, [password.root]); expect(Object.keys(bindings)).toEqual(["dwh", "evidence"]); expect(bindings.dwh.missing).toEqual([]); expect(supportsSessionRuntime(bindings)).toBe(true); }); function withEvidence(source: Record) { return parseWorkspaceYaml(`workspace: schema_version: 3 id: psd-clinical name: Policlinico San Donato language: it dwh: engine: postgres database: postgres 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] } evidence: source: ${JSON.stringify(source)} `); } const evidenceVariable = (suffix: string) => `THT_WS_PSD_CLINICAL_EVIDENCE_${suffix}`; test.each([ { type: "filesystem", uri: "workspace-content/psd-clinical/evidence" }, { type: "http", uris: ["https://evidence.example.test/guide.md"], authentication: "none" }, { type: "s3", uri: "s3://clinical-evidence/published/", credentials: "ambient" }, ])("does not resolve Evidence variables for $type modes without file credentials", (source) => { expect(resolveEvidenceBinding(withEvidence(source), { [evidenceVariable("SIGNED_URLS_FILE")]: "/CANARY/http", [evidenceVariable("ACCESS_KEY_FILE")]: "/CANARY/access", }, ["/run/secrets"])).toEqual({ values: {}, missing: [] }); }); test("requires only a safe HTTP signed-URL file and never reads its contents", () => { const signed = secretPath("evidence-signed-urls"); writeFileSync(signed.path, "CANARY-SIGNED-URL-CONTENT"); const source = withEvidence({ type: "http", uris: ["https://evidence.example.test/guide.md"], authentication: "signed_urls_file", }); const variable = evidenceVariable("SIGNED_URLS_FILE"); expect(resolveEvidenceBinding(source, {}, [signed.root]).missing).toEqual([variable]); const resolved = resolveEvidenceBinding(source, { [variable]: signed.path, [evidenceVariable("ACCESS_KEY_FILE")]: signed.path, }, [signed.root]); expect(resolved).toEqual({ values: { [variable]: realpathSync(signed.path) }, missing: [] }); expect(JSON.stringify(resolved)).not.toContain("CANARY-SIGNED-URL-CONTENT"); }); test("canonicalizes an in-root Evidence symlink before passing it to the harness", () => { const signed = secretPath("evidence-signed-target"); const link = join(signed.root, "signed-urls-link"); symlinkSync(signed.path, link); const source = withEvidence({ type: "http", uris: ["https://evidence.example.test/guide.md"], authentication: "signed_urls_file", }); const variable = evidenceVariable("SIGNED_URLS_FILE"); expect(resolveEvidenceBinding(source, { [variable]: link }, [signed.root])).toEqual({ values: { [variable]: realpathSync(signed.path) }, missing: [], }); }); test("requires S3 access and secret files together while accepting an optional safe session token", () => { const access = secretPath("evidence-access"); const secret = secretPath("evidence-secret"); const token = secretPath("evidence-token"); const source = withEvidence({ type: "s3", uri: "s3://clinical-evidence/published/", credentials: "static_files", }); const env = { [evidenceVariable("ACCESS_KEY_FILE")]: access.path, [evidenceVariable("SECRET_KEY_FILE")]: secret.path, [evidenceVariable("SESSION_TOKEN_FILE")]: token.path, [evidenceVariable("SIGNED_URLS_FILE")]: access.path, }; expect(resolveEvidenceBinding(source, { [evidenceVariable("ACCESS_KEY_FILE")]: access.path, }, [access.root]).missing).toEqual([evidenceVariable("SECRET_KEY_FILE")]); expect(resolveEvidenceBinding(source, env, [access.root, secret.root, token.root])).toEqual({ values: { [evidenceVariable("ACCESS_KEY_FILE")]: realpathSync(access.path), [evidenceVariable("SECRET_KEY_FILE")]: realpathSync(secret.path), [evidenceVariable("SESSION_TOKEN_FILE")]: realpathSync(token.path), }, missing: [], }); }); test("rejects relative, missing, directory, unreadable, and escaping symlink Evidence paths", () => { const allowed = secretPath("valid"); const outside = secretPath("outside"); const directory = join(allowed.root, "directory"); mkdirSync(directory); const link = join(allowed.root, "escape"); symlinkSync(outside.path, link); const unreadable = join(allowed.root, "unreadable"); writeFileSync(unreadable, "secret"); chmodSync(unreadable, 0o000); const source = withEvidence({ type: "http", uris: ["https://evidence.example.test/guide.md"], authentication: "signed_urls_file", }); const variable = evidenceVariable("SIGNED_URLS_FILE"); for (const path of ["relative", join(allowed.root, "missing"), directory, unreadable, link]) { expect(resolveEvidenceBinding(source, { [variable]: path }, [allowed.root])).toEqual({ values: {}, missing: [variable], }); } chmodSync(unreadable, 0o600); }); test("includes Evidence binding completeness in session runtime support without changing v3 compatibility", () => { const unsigned = resolveRuntimeBindings(workspaceV3, {}, ["/run/secrets"]); expect(unsigned.evidence).toEqual({ values: {}, missing: [] }); expect(supportsSessionRuntime(unsigned)).toBe(true); const signed = resolveRuntimeBindings(withEvidence({ type: "http", uris: ["https://evidence.example.test/guide.md"], authentication: "signed_urls_file", }), {}, ["/run/secrets"]); expect(signed.evidence.missing).toEqual([evidenceVariable("SIGNED_URLS_FILE")]); expect(supportsSessionRuntime(signed)).toBe(false); });