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 workspace = parseWorkspaceYaml(`workspace: schema_version: 2 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: pgvector database: postgres schema: vectors collection: clinical_documents dimensions: 768 distance: cosine supported_transports: [pgvector_direct, rest_api, ssh_tunnel] embedding: provider: ollama_compatible model: nomic-embed-text-v2-moe dimensions: 768 llm_policy: allowed: [zai/glm-5.2] `); 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] 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("marks a portable workspace non-activatable when its local REST key file is absent", () => { const result = resolveBinding(workspace, "DWH", { THT_WS_PSD_CLINICAL_DWH_TRANSPORT: "rest_api", THT_WS_PSD_CLINICAL_DWH_BASE_URL: "https://dwh.example.test", }, ["/run/secrets"]); expect(result.missing).toContain("THT_WS_PSD_CLINICAL_DWH_API_KEY_FILE"); }); test("does not require a REST secret file when its declared diagnostic uses auth none", () => { const unauthenticatedWorkspace = parseWorkspaceYaml(`workspace: schema_version: 2 id: psd-clinical name: Policlinico San Donato language: it dwh: engine: postgres database: postgres schema: datawarehouse supported_transports: [rest_api] semantic_index: vector_store: engine: pgvector database: postgres 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: POST path: /rpc/ping auth: none response: { database: database, schema: schema } vector_rest: metadata: method: GET path: /vector/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 bindings = resolveRuntimeBindings(unauthenticatedWorkspace, { THT_WS_PSD_CLINICAL_DWH_TRANSPORT: "rest_api", THT_WS_PSD_CLINICAL_DWH_BASE_URL: "https://dwh.example.test", THT_WS_PSD_CLINICAL_VECTOR_TRANSPORT: "rest_api", THT_WS_PSD_CLINICAL_VECTOR_BASE_URL: "https://vector.example.test", THT_WS_PSD_CLINICAL_EMBEDDING_BASE_URL: "https://embedding.example.test", }, ["/run/secrets"]); expect(bindings.dwh.missing).toEqual([]); expect(bindings.vector.missing).toEqual([]); expect(bindings.embedding.missing).toEqual([]); }); test("resolves direct bindings from the stable workspace namespace", () => { const password = secretPath("dwh-password"); const result = resolveBinding(workspace, "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: "thoth_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("reports only a FILE variable name when a secret path is outside the configured roots", () => { const outside = secretPath("outside-password"); const allowed = secretPath("allowed-password"); const result = resolveBinding(workspace, "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: "thoth_reader", THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE: outside.path, }, [allowed.root]); expect(result.missing).toEqual(["THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE"]); expect(result.missing.join("\n")).not.toContain(outside.path); }); test("reports an invalid optional secret file instead of silently dropping it", () => { const password = secretPath("dwh-password"); const result = resolveBinding(workspace, "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: "thoth_reader", THT_WS_PSD_CLINICAL_DWH_PASSWORD_FILE: password.path, THT_WS_PSD_CLINICAL_DWH_TLS_CA_FILE: "relative-ca.pem", }, [password.root]); expect(result.missing).toContain("THT_WS_PSD_CLINICAL_DWH_TLS_CA_FILE"); expect(result.values).not.toHaveProperty("THT_WS_PSD_CLINICAL_DWH_TLS_CA_FILE"); }); test("rejects a selected transport that the canonical workspace does not support", () => { const directOnly = { ...workspace, dwh: { ...workspace.dwh, supported_transports: ["postgres_direct"] }, }; const result = resolveBinding(directOnly, "DWH", { THT_WS_PSD_CLINICAL_DWH_TRANSPORT: "rest_api", }, ["/run/secrets"]); expect(result).toMatchObject({ transport: "rest_api", missing: ["THT_WS_PSD_CLINICAL_DWH_TRANSPORT"], }); }); test("never treats a vector reader credential as the optional writer binding", () => { const readerKey = secretPath("vector-reader-key"); const writerWorkspace = { ...workspace, semantic_index: { ...workspace.semantic_index, vector_writer: {} }, }; const resolveWriter = () => resolveBinding(writerWorkspace, "VECTOR_WRITER" as never, { THT_WS_PSD_CLINICAL_VECTOR_API_KEY_FILE: readerKey.path, }, [readerKey.root]); expect(resolveWriter).not.toThrow(); expect(resolveWriter()).toMatchObject({ missing: ["THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE"], values: {}, }); }); test("fails closed for v3 external semantic bindings", () => { expect(() => resolveBinding(workspaceV3, "VECTOR", {}, ["/run/secrets"])) .not.toThrow(); expect(() => resolveBinding(workspaceV3, "EMBEDDING", {}, ["/run/secrets"])) .not.toThrow(); expect(() => resolveRuntimeBindings(workspaceV3, { THT_WS_PSD_CLINICAL_VECTOR_TRANSPORT: "rest_api", THT_WS_PSD_CLINICAL_VECTOR_BASE_URL: "https://vector.example.test", THT_WS_PSD_CLINICAL_EMBEDDING_BASE_URL: "https://embedding.example.test", THT_WS_PSD_CLINICAL_VECTOR_API_KEY_FILE: "/run/secrets/vector-api-key", }, ["/run/secrets"])).not.toThrow(); }); test("schema v3 ignores external semantic binding variables and reports only DWH requirements", () => { 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_TRANSPORT: "rest_api", THT_WS_PSD_CLINICAL_VECTOR_BASE_URL: "https://vector.example.test", THT_WS_PSD_CLINICAL_VECTOR_API_KEY_FILE: "/run/secrets/vector-api-key", THT_WS_PSD_CLINICAL_EMBEDDING_BASE_URL: "https://embedding.example.test", THT_WS_PSD_CLINICAL_EMBEDDING_API_KEY_FILE: "/run/secrets/embedding-api-key", }, [password.root]); expect(bindings.dwh.missing).toEqual([]); expect(bindings.vector.missing).toEqual([]); expect(bindings.embedding.missing).toEqual([]); expect(bindings.vector.values).toEqual({}); expect(bindings.embedding.values).toEqual({}); }); 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); });