388 lines
14 KiB
TypeScript
388 lines
14 KiB
TypeScript
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<string, unknown>) {
|
|
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);
|
|
});
|