fix: fail closed v3 legacy semantic fallbacks
This commit is contained in:
@@ -4,7 +4,7 @@ import { buildInstallationContract, type InstallationRole, type InstallationSuff
|
|||||||
import {
|
import {
|
||||||
DWH_TRANSPORTS,
|
DWH_TRANSPORTS,
|
||||||
VECTOR_TRANSPORTS,
|
VECTOR_TRANSPORTS,
|
||||||
validateCanonicalWorkspace,
|
validateWorkspaceDescriptor,
|
||||||
type DwhTransport,
|
type DwhTransport,
|
||||||
type VectorTransport,
|
type VectorTransport,
|
||||||
type WorkspaceDescriptor,
|
type WorkspaceDescriptor,
|
||||||
@@ -95,14 +95,20 @@ export function resolveBinding(
|
|||||||
env: NodeJS.ProcessEnv,
|
env: NodeJS.ProcessEnv,
|
||||||
secretRoots: readonly string[],
|
secretRoots: readonly string[],
|
||||||
): ResolvedBinding {
|
): ResolvedBinding {
|
||||||
const canonical = validateCanonicalWorkspace(workspace);
|
const descriptor = validateWorkspaceDescriptor(workspace);
|
||||||
const contract = buildInstallationContract(canonical);
|
if (descriptor.workspace.schema_version === 3 && role !== "DWH") {
|
||||||
|
throw new Error("Schema version 3 semantic bindings are not supported by the legacy installation contract");
|
||||||
|
}
|
||||||
|
|
||||||
|
const contract = buildInstallationContract(descriptor);
|
||||||
const variables = contract.variables.filter((variable) => variable.role === role);
|
const variables = contract.variables.filter((variable) => variable.role === role);
|
||||||
const transportVariable = variables.find((variable) => variable.suffix === "TRANSPORT");
|
const transportVariable = variables.find((variable) => variable.suffix === "TRANSPORT");
|
||||||
const supported = role === "DWH"
|
const supported = role === "DWH"
|
||||||
? canonical.dwh.supported_transports
|
? descriptor.dwh.supported_transports
|
||||||
: role === "VECTOR"
|
: role === "VECTOR"
|
||||||
? (canonical.semantic_index.vector_store.supported_transports ?? [])
|
? ("supported_transports" in descriptor.semantic_index.vector_store
|
||||||
|
? descriptor.semantic_index.vector_store.supported_transports
|
||||||
|
: [])
|
||||||
: ["rest_api"] as const;
|
: ["rest_api"] as const;
|
||||||
const selectedValue = transportVariable ? env[transportVariable.name] : undefined;
|
const selectedValue = transportVariable ? env[transportVariable.name] : undefined;
|
||||||
const selectedTransport = isTransport(selectedValue) ? selectedValue : supported[0];
|
const selectedTransport = isTransport(selectedValue) ? selectedValue : supported[0];
|
||||||
@@ -112,7 +118,7 @@ export function resolveBinding(
|
|||||||
missing.push(transportVariable.name);
|
missing.push(transportVariable.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
const required = new Set(requiredSuffixes(canonical, role, selectedTransport));
|
const required = new Set(requiredSuffixes(descriptor, role, selectedTransport));
|
||||||
const values: Record<string, string> = {};
|
const values: Record<string, string> = {};
|
||||||
for (const variable of variables) {
|
for (const variable of variables) {
|
||||||
if (variable.suffix === "TRANSPORT") continue;
|
if (variable.suffix === "TRANSPORT") continue;
|
||||||
@@ -136,11 +142,16 @@ export function resolveRuntimeBindings(
|
|||||||
env: NodeJS.ProcessEnv,
|
env: NodeJS.ProcessEnv,
|
||||||
secretRoots: readonly string[],
|
secretRoots: readonly string[],
|
||||||
): RuntimeBindings {
|
): RuntimeBindings {
|
||||||
|
const descriptor = validateWorkspaceDescriptor(workspace);
|
||||||
|
if (descriptor.workspace.schema_version === 3) {
|
||||||
|
throw new Error("Schema version 3 semantic runtime bindings are not supported before the internal Qdrant/Ollama runtime lands");
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dwh: resolveBinding(workspace, "DWH", env, secretRoots),
|
dwh: resolveBinding(descriptor, "DWH", env, secretRoots),
|
||||||
vector: resolveBinding(workspace, "VECTOR", env, secretRoots),
|
vector: resolveBinding(descriptor, "VECTOR", env, secretRoots),
|
||||||
vectorWriter: resolveBinding(workspace, "VECTOR_WRITER", env, secretRoots),
|
vectorWriter: resolveBinding(descriptor, "VECTOR_WRITER", env, secretRoots),
|
||||||
embedding: resolveBinding(workspace, "EMBEDDING", env, secretRoots),
|
embedding: resolveBinding(descriptor, "EMBEDDING", env, secretRoots),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { validateCanonicalWorkspace } from "./schema.js";
|
import { validateWorkspaceDescriptor } from "./schema.js";
|
||||||
import type { DwhTransport, VectorTransport, WorkspaceDescriptor } from "./schema.js";
|
import type { DwhTransport, VectorTransport, WorkspaceDescriptor } from "./schema.js";
|
||||||
|
|
||||||
export type InstallationRole = "DWH" | "VECTOR" | "VECTOR_WRITER" | "EMBEDDING";
|
export type InstallationRole = "DWH" | "VECTOR" | "VECTOR_WRITER" | "EMBEDDING";
|
||||||
@@ -120,23 +120,29 @@ function connectorVariables(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildInstallationContract(workspace: WorkspaceDescriptor): InstallationContract {
|
export function buildInstallationContract(workspace: WorkspaceDescriptor): InstallationContract {
|
||||||
const canonical = validateCanonicalWorkspace(workspace);
|
const descriptor = validateWorkspaceDescriptor(workspace);
|
||||||
const namespace = namespaceFor(canonical);
|
const namespace = namespaceFor(descriptor);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
workspaceId: canonical.workspace.id,
|
workspaceId: descriptor.workspace.id,
|
||||||
namespace,
|
namespace,
|
||||||
variables: [
|
variables: [
|
||||||
...connectorVariables(namespace, "DWH", canonical.dwh.supported_transports),
|
...connectorVariables(namespace, "DWH", descriptor.dwh.supported_transports),
|
||||||
...connectorVariables(
|
...(descriptor.workspace.schema_version === 2
|
||||||
namespace,
|
? connectorVariables(
|
||||||
"VECTOR",
|
namespace,
|
||||||
canonical.semantic_index.vector_store.supported_transports ?? [],
|
"VECTOR",
|
||||||
),
|
"supported_transports" in descriptor.semantic_index.vector_store
|
||||||
...(canonical.semantic_index.vector_writer
|
? descriptor.semantic_index.vector_store.supported_transports
|
||||||
|
: [],
|
||||||
|
)
|
||||||
|
: []),
|
||||||
|
...(descriptor.workspace.schema_version === 2 && descriptor.semantic_index.vector_writer
|
||||||
? [createVariable(namespace, "VECTOR_WRITER", "API_KEY_FILE")]
|
? [createVariable(namespace, "VECTOR_WRITER", "API_KEY_FILE")]
|
||||||
: []),
|
: []),
|
||||||
...EMBEDDING_SUFFIXES.map((suffix) => createVariable(namespace, "EMBEDDING", suffix)),
|
...(descriptor.workspace.schema_version === 2
|
||||||
|
? EMBEDDING_SUFFIXES.map((suffix) => createVariable(namespace, "EMBEDDING", suffix))
|
||||||
|
: []),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -148,8 +154,8 @@ function localizedIntroduction(workspace: WorkspaceDescriptor): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function renderWorkspaceDocs(workspace: WorkspaceDescriptor): { envExample: string; markdown: string } {
|
export function renderWorkspaceDocs(workspace: WorkspaceDescriptor): { envExample: string; markdown: string } {
|
||||||
const canonical = validateCanonicalWorkspace(workspace);
|
const descriptor = validateWorkspaceDescriptor(workspace);
|
||||||
const contract = buildInstallationContract(canonical);
|
const contract = buildInstallationContract(descriptor);
|
||||||
const variablesByRole = new Map<InstallationRole, InstallationVariable[]>();
|
const variablesByRole = new Map<InstallationRole, InstallationVariable[]>();
|
||||||
for (const variable of contract.variables) {
|
for (const variable of contract.variables) {
|
||||||
const variables = variablesByRole.get(variable.role) ?? [];
|
const variables = variablesByRole.get(variable.role) ?? [];
|
||||||
@@ -158,7 +164,7 @@ export function renderWorkspaceDocs(workspace: WorkspaceDescriptor): { envExampl
|
|||||||
}
|
}
|
||||||
|
|
||||||
const envExample = [
|
const envExample = [
|
||||||
`# Generated installation bindings for ${canonical.workspace.id}`,
|
`# Generated installation bindings for ${descriptor.workspace.id}`,
|
||||||
"# Provide secret file paths only; never paste secret values here.",
|
"# Provide secret file paths only; never paste secret values here.",
|
||||||
...contract.variables.map((variable) => `${variable.name}=`),
|
...contract.variables.map((variable) => `${variable.name}=`),
|
||||||
"",
|
"",
|
||||||
@@ -167,9 +173,9 @@ export function renderWorkspaceDocs(workspace: WorkspaceDescriptor): { envExampl
|
|||||||
const markdown = [
|
const markdown = [
|
||||||
"# Installation requirements",
|
"# Installation requirements",
|
||||||
"",
|
"",
|
||||||
`**Workspace:** ${canonical.workspace.name}`,
|
`**Workspace:** ${descriptor.workspace.name}`,
|
||||||
"",
|
"",
|
||||||
localizedIntroduction(canonical),
|
localizedIntroduction(descriptor),
|
||||||
"",
|
"",
|
||||||
"Use the following UI fields as installation bindings. Secret fields always contain file paths, never secret values.",
|
"Use the following UI fields as installation bindings. Secret fields always contain file paths, never secret values.",
|
||||||
"",
|
"",
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import { buildInstallationContract } from "./contracts.js";
|
|||||||
import type { RuntimeBindings } from "./runtime-renderer.js";
|
import type { RuntimeBindings } from "./runtime-renderer.js";
|
||||||
import {
|
import {
|
||||||
resolveDiagnosticUrl,
|
resolveDiagnosticUrl,
|
||||||
validateCanonicalWorkspace,
|
validateWorkspaceDescriptor,
|
||||||
type CanonicalWorkspace,
|
|
||||||
type RestDiagnosticRequest,
|
type RestDiagnosticRequest,
|
||||||
|
type WorkspaceV2,
|
||||||
type WorkspaceDescriptor,
|
type WorkspaceDescriptor,
|
||||||
} from "./schema.js";
|
} from "./schema.js";
|
||||||
import type { WorkspaceErrorCode } from "./types.js";
|
import type { WorkspaceErrorCode } from "./types.js";
|
||||||
@@ -542,7 +542,7 @@ function diagnosticError(code: WorkspaceErrorCode, field?: string): Diagnostic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function bindingName(
|
function bindingName(
|
||||||
workspace: CanonicalWorkspace,
|
workspace: WorkspaceV2,
|
||||||
role: "DWH" | "VECTOR" | "VECTOR_WRITER" | "EMBEDDING",
|
role: "DWH" | "VECTOR" | "VECTOR_WRITER" | "EMBEDDING",
|
||||||
suffix: string,
|
suffix: string,
|
||||||
): string {
|
): string {
|
||||||
@@ -559,7 +559,7 @@ function numericBinding(binding: Record<string, string>, name: string): number |
|
|||||||
}
|
}
|
||||||
|
|
||||||
function diagnosticsForMissingBindings(
|
function diagnosticsForMissingBindings(
|
||||||
workspace: CanonicalWorkspace,
|
workspace: WorkspaceV2,
|
||||||
bindings: RuntimeBindings,
|
bindings: RuntimeBindings,
|
||||||
): Diagnostic[] {
|
): Diagnostic[] {
|
||||||
const missing = new Set([
|
const missing = new Set([
|
||||||
@@ -576,7 +576,7 @@ function diagnosticsForMissingBindings(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function connectorRequest(
|
function connectorRequest(
|
||||||
workspace: CanonicalWorkspace,
|
workspace: WorkspaceV2,
|
||||||
role: ConnectorRole,
|
role: ConnectorRole,
|
||||||
bindings: RuntimeBindings,
|
bindings: RuntimeBindings,
|
||||||
timeoutMs: number,
|
timeoutMs: number,
|
||||||
@@ -650,7 +650,7 @@ function connectorRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function tunnelProbeRequest(
|
function tunnelProbeRequest(
|
||||||
workspace: CanonicalWorkspace,
|
workspace: WorkspaceV2,
|
||||||
role: ConnectorRole,
|
role: ConnectorRole,
|
||||||
bindings: RuntimeBindings,
|
bindings: RuntimeBindings,
|
||||||
timeoutMs: number,
|
timeoutMs: number,
|
||||||
@@ -694,7 +694,11 @@ export function createWorkspaceDiagnoser(
|
|||||||
bindings: RuntimeBindings,
|
bindings: RuntimeBindings,
|
||||||
options: { writeProbe: boolean },
|
options: { writeProbe: boolean },
|
||||||
): Promise<WorkspaceDiagnostics> {
|
): Promise<WorkspaceDiagnostics> {
|
||||||
const canonical = validateCanonicalWorkspace(workspace);
|
const descriptor = validateWorkspaceDescriptor(workspace);
|
||||||
|
if (descriptor.workspace.schema_version !== 2) {
|
||||||
|
return { activatable: false, diagnostics: [diagnosticError("workspace_not_activatable")] };
|
||||||
|
}
|
||||||
|
const canonical = descriptor as WorkspaceV2;
|
||||||
const diagnostics = diagnosticsForMissingBindings(canonical, bindings);
|
const diagnostics = diagnosticsForMissingBindings(canonical, bindings);
|
||||||
if (diagnostics.length > 0) return { activatable: false, diagnostics };
|
if (diagnostics.length > 0) return { activatable: false, diagnostics };
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { stringify } from "yaml";
|
import { stringify } from "yaml";
|
||||||
import { buildInstallationContract } from "./contracts.js";
|
import { buildInstallationContract } from "./contracts.js";
|
||||||
import { validateCanonicalWorkspace, type WorkspaceDescriptor } from "./schema.js";
|
import { validateWorkspaceDescriptor, type WorkspaceDescriptor, type WorkspaceV2 } from "./schema.js";
|
||||||
import type { ResolvedBinding, RuntimeBindings } from "./bindings.js";
|
import type { ResolvedBinding, RuntimeBindings } from "./bindings.js";
|
||||||
export type { RuntimeBindings } from "./bindings.js";
|
export type { RuntimeBindings } from "./bindings.js";
|
||||||
|
|
||||||
@@ -86,7 +86,15 @@ export function renderRuntimeConfig(
|
|||||||
identity?: RuntimeIdentity,
|
identity?: RuntimeIdentity,
|
||||||
installation: RuntimeInstallationOverlay = {},
|
installation: RuntimeInstallationOverlay = {},
|
||||||
): string {
|
): string {
|
||||||
const canonical = validateCanonicalWorkspace(workspace);
|
const descriptor = validateWorkspaceDescriptor(workspace);
|
||||||
|
if (descriptor.workspace.schema_version !== 2) {
|
||||||
|
if (descriptor.workspace.schema_version === 1) {
|
||||||
|
throw new Error("Workspace descriptor requires explicit migration to schema version 2");
|
||||||
|
}
|
||||||
|
throw new Error("Schema version 3 runtime rendering is unsupported until the internal semantic runtime is implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
const canonical = descriptor as WorkspaceV2;
|
||||||
if ([...bindings.dwh.missing, ...bindings.vector.missing, ...bindings.embedding.missing].length > 0) {
|
if ([...bindings.dwh.missing, ...bindings.vector.missing, ...bindings.embedding.missing].length > 0) {
|
||||||
throw new Error("runtime configuration requires complete bindings");
|
throw new Error("runtime configuration requires complete bindings");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,11 +102,6 @@ interface QdrantVectorStore {
|
|||||||
collection: string;
|
collection: string;
|
||||||
dimensions: 1024;
|
dimensions: 1024;
|
||||||
distance: "cosine";
|
distance: "cosine";
|
||||||
database?: string;
|
|
||||||
schema?: string;
|
|
||||||
port?: number;
|
|
||||||
timeout_ms?: number;
|
|
||||||
supported_transports?: VectorTransport[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkspaceV3 extends WorkspaceBase<3, QdrantVectorStore> {}
|
export interface WorkspaceV3 extends WorkspaceBase<3, QdrantVectorStore> {}
|
||||||
|
|||||||
@@ -21,18 +21,12 @@ export interface QdrantVectorStore {
|
|||||||
collection: string;
|
collection: string;
|
||||||
dimensions: 1024;
|
dimensions: 1024;
|
||||||
distance: "cosine";
|
distance: "cosine";
|
||||||
database?: string;
|
|
||||||
schema?: string;
|
|
||||||
port?: number;
|
|
||||||
timeout_ms?: number;
|
|
||||||
supported_transports?: ("pgvector_direct" | "rest_api" | "ssh_tunnel")[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InternalEmbedding {
|
export interface InternalEmbedding {
|
||||||
provider: "ollama_internal";
|
provider: "ollama_internal";
|
||||||
model: "qwen3-embedding:0.6b";
|
model: "qwen3-embedding:0.6b";
|
||||||
dimensions: 1024;
|
dimensions: 1024;
|
||||||
timeout_ms?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkspaceV2 {
|
export interface WorkspaceV2 {
|
||||||
@@ -113,5 +107,5 @@ export interface WorkspaceV3 {
|
|||||||
embedding: InternalEmbedding;
|
embedding: InternalEmbedding;
|
||||||
};
|
};
|
||||||
llm_policy: WorkspaceV2["llm_policy"];
|
llm_policy: WorkspaceV2["llm_policy"];
|
||||||
diagnostics?: WorkspaceV2["diagnostics"];
|
diagnostics?: Pick<NonNullable<WorkspaceV2["diagnostics"]>, "dwh_rest">;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,29 @@ semantic_index:
|
|||||||
llm_policy:
|
llm_policy:
|
||||||
allowed: [zai/glm-5.2]
|
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 paths: RuntimePaths = {
|
const paths: RuntimePaths = {
|
||||||
sessions: "/data/workspaces/psd-clinical/sessions",
|
sessions: "/data/workspaces/psd-clinical/sessions",
|
||||||
artifacts: "/data/workspaces/psd-clinical/artifacts",
|
artifacts: "/data/workspaces/psd-clinical/artifacts",
|
||||||
@@ -149,6 +172,12 @@ test("refuses to render a v1 descriptor until an explicit migration creates v2",
|
|||||||
expect(() => renderRuntimeConfig(legacyWorkspace, directBindings, paths)).toThrow(/migrat/i);
|
expect(() => renderRuntimeConfig(legacyWorkspace, directBindings, paths)).toThrow(/migrat/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("fails closed for v3 runtime rendering and session support", () => {
|
||||||
|
expect(supportsSessionRuntime(directBindings)).toBe(true);
|
||||||
|
expect(() => renderRuntimeConfig(workspaceV3, directBindings, paths))
|
||||||
|
.toThrow(/unsupported|schema version 3|qdrant|ollama_internal/i);
|
||||||
|
});
|
||||||
|
|
||||||
test("omits direct TLS fields when binding validation did not retain a file path", () => {
|
test("omits direct TLS fields when binding validation did not retain a file path", () => {
|
||||||
const dwhValues = { ...directBindings.dwh.values };
|
const dwhValues = { ...directBindings.dwh.values };
|
||||||
const vectorValues = { ...directBindings.vector.values };
|
const vectorValues = { ...directBindings.vector.values };
|
||||||
|
|||||||
@@ -31,6 +31,29 @@ semantic_index:
|
|||||||
llm_policy:
|
llm_policy:
|
||||||
allowed: [zai/glm-5.2]
|
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[] = [];
|
const temporaryRoots: string[] = [];
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -196,3 +219,12 @@ test("never treats a vector reader credential as the optional writer binding", (
|
|||||||
values: {},
|
values: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("fails closed for v3 external semantic bindings", () => {
|
||||||
|
expect(() => resolveBinding(workspaceV3, "VECTOR", {}, ["/run/secrets"]))
|
||||||
|
.toThrow(/unsupported|schema version 3|semantic/i);
|
||||||
|
expect(() => resolveBinding(workspaceV3, "EMBEDDING", {}, ["/run/secrets"]))
|
||||||
|
.toThrow(/unsupported|schema version 3|semantic/i);
|
||||||
|
expect(() => resolveRuntimeBindings(workspaceV3, {}, ["/run/secrets"]))
|
||||||
|
.toThrow(/unsupported|schema version 3|semantic/i);
|
||||||
|
});
|
||||||
|
|||||||
@@ -39,6 +39,29 @@ llm_policy:
|
|||||||
- zai/glm-5.2
|
- zai/glm-5.2
|
||||||
- openai/gpt-5
|
- openai/gpt-5
|
||||||
`);
|
`);
|
||||||
|
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]
|
||||||
|
`);
|
||||||
|
|
||||||
test("generates stable FILE-based secret requirements from an immutable ID", () => {
|
test("generates stable FILE-based secret requirements from an immutable ID", () => {
|
||||||
const contract = buildInstallationContract(validWorkspace);
|
const contract = buildInstallationContract(validWorkspace);
|
||||||
@@ -278,3 +301,13 @@ test("validates public contract and documentation inputs at runtime", () => {
|
|||||||
expect(() => buildInstallationContract(unsafeWorkspace)).toThrow(/id/i);
|
expect(() => buildInstallationContract(unsafeWorkspace)).toThrow(/id/i);
|
||||||
expect(() => renderWorkspaceDocs(unsafeWorkspace)).toThrow(/id/i);
|
expect(() => renderWorkspaceDocs(unsafeWorkspace)).toThrow(/id/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("v3 installation contract omits external vector and embedding bindings", () => {
|
||||||
|
const contract = buildInstallationContract(workspaceV3);
|
||||||
|
const names = contract.variables.map((variable) => variable.name);
|
||||||
|
|
||||||
|
expect(names).toContain("THT_WS_PSD_CLINICAL_DWH_TRANSPORT");
|
||||||
|
expect(names.some((name) => name.includes("_VECTOR_"))).toBe(false);
|
||||||
|
expect(names.some((name) => name.includes("_EMBEDDING_"))).toBe(false);
|
||||||
|
expect(renderWorkspaceDocs(workspaceV3).markdown).not.toContain("Embedding service");
|
||||||
|
});
|
||||||
|
|||||||
@@ -128,6 +128,18 @@ test("rejects unknown fields in schema v3 semantic identity", () => {
|
|||||||
expect(() => parseWorkspaceYaml(withUnknownField)).toThrow(/unrecognized key/i);
|
expect(() => parseWorkspaceYaml(withUnknownField)).toThrow(/unrecognized key/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("rejects legacy semantic connector fields and diagnostics in schema v3", () => {
|
||||||
|
expect(() => parseWorkspaceYaml(validYaml.replace(
|
||||||
|
" collection: psd-clinical\n",
|
||||||
|
" collection: psd-clinical\n database: postgres\n",
|
||||||
|
))).toThrow(/unrecognized key|database/i);
|
||||||
|
|
||||||
|
expect(() => parseWorkspaceYaml(validYaml.replace(
|
||||||
|
"llm_policy:\n",
|
||||||
|
"diagnostics:\n vector_rest:\n metadata:\n method: GET\n path: /metadata\n auth: bearer\n response:\n collection: collection\n dimensions: dimensions\n distance: distance\nllm_policy:\n",
|
||||||
|
))).toThrow(/unrecognized key|vector_rest/i);
|
||||||
|
});
|
||||||
|
|
||||||
test("keeps v1 and v2 descriptors parseable but non-operational", () => {
|
test("keeps v1 and v2 descriptors parseable but non-operational", () => {
|
||||||
const v1Yaml = `workspace:
|
const v1Yaml = `workspace:
|
||||||
schema_version: 1
|
schema_version: 1
|
||||||
|
|||||||
Reference in New Issue
Block a user