fix: fail closed v3 legacy semantic fallbacks
This commit is contained in:
@@ -4,7 +4,7 @@ import { buildInstallationContract, type InstallationRole, type InstallationSuff
|
||||
import {
|
||||
DWH_TRANSPORTS,
|
||||
VECTOR_TRANSPORTS,
|
||||
validateCanonicalWorkspace,
|
||||
validateWorkspaceDescriptor,
|
||||
type DwhTransport,
|
||||
type VectorTransport,
|
||||
type WorkspaceDescriptor,
|
||||
@@ -95,14 +95,20 @@ export function resolveBinding(
|
||||
env: NodeJS.ProcessEnv,
|
||||
secretRoots: readonly string[],
|
||||
): ResolvedBinding {
|
||||
const canonical = validateCanonicalWorkspace(workspace);
|
||||
const contract = buildInstallationContract(canonical);
|
||||
const descriptor = validateWorkspaceDescriptor(workspace);
|
||||
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 transportVariable = variables.find((variable) => variable.suffix === "TRANSPORT");
|
||||
const supported = role === "DWH"
|
||||
? canonical.dwh.supported_transports
|
||||
? descriptor.dwh.supported_transports
|
||||
: 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;
|
||||
const selectedValue = transportVariable ? env[transportVariable.name] : undefined;
|
||||
const selectedTransport = isTransport(selectedValue) ? selectedValue : supported[0];
|
||||
@@ -112,7 +118,7 @@ export function resolveBinding(
|
||||
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> = {};
|
||||
for (const variable of variables) {
|
||||
if (variable.suffix === "TRANSPORT") continue;
|
||||
@@ -136,11 +142,16 @@ export function resolveRuntimeBindings(
|
||||
env: NodeJS.ProcessEnv,
|
||||
secretRoots: readonly string[],
|
||||
): 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 {
|
||||
dwh: resolveBinding(workspace, "DWH", env, secretRoots),
|
||||
vector: resolveBinding(workspace, "VECTOR", env, secretRoots),
|
||||
vectorWriter: resolveBinding(workspace, "VECTOR_WRITER", env, secretRoots),
|
||||
embedding: resolveBinding(workspace, "EMBEDDING", env, secretRoots),
|
||||
dwh: resolveBinding(descriptor, "DWH", env, secretRoots),
|
||||
vector: resolveBinding(descriptor, "VECTOR", env, secretRoots),
|
||||
vectorWriter: resolveBinding(descriptor, "VECTOR_WRITER", 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";
|
||||
|
||||
export type InstallationRole = "DWH" | "VECTOR" | "VECTOR_WRITER" | "EMBEDDING";
|
||||
@@ -120,23 +120,29 @@ function connectorVariables(
|
||||
}
|
||||
|
||||
export function buildInstallationContract(workspace: WorkspaceDescriptor): InstallationContract {
|
||||
const canonical = validateCanonicalWorkspace(workspace);
|
||||
const namespace = namespaceFor(canonical);
|
||||
const descriptor = validateWorkspaceDescriptor(workspace);
|
||||
const namespace = namespaceFor(descriptor);
|
||||
|
||||
return {
|
||||
workspaceId: canonical.workspace.id,
|
||||
workspaceId: descriptor.workspace.id,
|
||||
namespace,
|
||||
variables: [
|
||||
...connectorVariables(namespace, "DWH", canonical.dwh.supported_transports),
|
||||
...connectorVariables(
|
||||
namespace,
|
||||
"VECTOR",
|
||||
canonical.semantic_index.vector_store.supported_transports ?? [],
|
||||
),
|
||||
...(canonical.semantic_index.vector_writer
|
||||
...connectorVariables(namespace, "DWH", descriptor.dwh.supported_transports),
|
||||
...(descriptor.workspace.schema_version === 2
|
||||
? connectorVariables(
|
||||
namespace,
|
||||
"VECTOR",
|
||||
"supported_transports" in descriptor.semantic_index.vector_store
|
||||
? descriptor.semantic_index.vector_store.supported_transports
|
||||
: [],
|
||||
)
|
||||
: []),
|
||||
...(descriptor.workspace.schema_version === 2 && descriptor.semantic_index.vector_writer
|
||||
? [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 } {
|
||||
const canonical = validateCanonicalWorkspace(workspace);
|
||||
const contract = buildInstallationContract(canonical);
|
||||
const descriptor = validateWorkspaceDescriptor(workspace);
|
||||
const contract = buildInstallationContract(descriptor);
|
||||
const variablesByRole = new Map<InstallationRole, InstallationVariable[]>();
|
||||
for (const variable of contract.variables) {
|
||||
const variables = variablesByRole.get(variable.role) ?? [];
|
||||
@@ -158,7 +164,7 @@ export function renderWorkspaceDocs(workspace: WorkspaceDescriptor): { envExampl
|
||||
}
|
||||
|
||||
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.",
|
||||
...contract.variables.map((variable) => `${variable.name}=`),
|
||||
"",
|
||||
@@ -167,9 +173,9 @@ export function renderWorkspaceDocs(workspace: WorkspaceDescriptor): { envExampl
|
||||
const markdown = [
|
||||
"# 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.",
|
||||
"",
|
||||
|
||||
@@ -10,9 +10,9 @@ import { buildInstallationContract } from "./contracts.js";
|
||||
import type { RuntimeBindings } from "./runtime-renderer.js";
|
||||
import {
|
||||
resolveDiagnosticUrl,
|
||||
validateCanonicalWorkspace,
|
||||
type CanonicalWorkspace,
|
||||
validateWorkspaceDescriptor,
|
||||
type RestDiagnosticRequest,
|
||||
type WorkspaceV2,
|
||||
type WorkspaceDescriptor,
|
||||
} from "./schema.js";
|
||||
import type { WorkspaceErrorCode } from "./types.js";
|
||||
@@ -542,7 +542,7 @@ function diagnosticError(code: WorkspaceErrorCode, field?: string): Diagnostic {
|
||||
}
|
||||
|
||||
function bindingName(
|
||||
workspace: CanonicalWorkspace,
|
||||
workspace: WorkspaceV2,
|
||||
role: "DWH" | "VECTOR" | "VECTOR_WRITER" | "EMBEDDING",
|
||||
suffix: string,
|
||||
): string {
|
||||
@@ -559,7 +559,7 @@ function numericBinding(binding: Record<string, string>, name: string): number |
|
||||
}
|
||||
|
||||
function diagnosticsForMissingBindings(
|
||||
workspace: CanonicalWorkspace,
|
||||
workspace: WorkspaceV2,
|
||||
bindings: RuntimeBindings,
|
||||
): Diagnostic[] {
|
||||
const missing = new Set([
|
||||
@@ -576,7 +576,7 @@ function diagnosticsForMissingBindings(
|
||||
}
|
||||
|
||||
function connectorRequest(
|
||||
workspace: CanonicalWorkspace,
|
||||
workspace: WorkspaceV2,
|
||||
role: ConnectorRole,
|
||||
bindings: RuntimeBindings,
|
||||
timeoutMs: number,
|
||||
@@ -650,7 +650,7 @@ function connectorRequest(
|
||||
}
|
||||
|
||||
function tunnelProbeRequest(
|
||||
workspace: CanonicalWorkspace,
|
||||
workspace: WorkspaceV2,
|
||||
role: ConnectorRole,
|
||||
bindings: RuntimeBindings,
|
||||
timeoutMs: number,
|
||||
@@ -694,7 +694,11 @@ export function createWorkspaceDiagnoser(
|
||||
bindings: RuntimeBindings,
|
||||
options: { writeProbe: boolean },
|
||||
): 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);
|
||||
if (diagnostics.length > 0) return { activatable: false, diagnostics };
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { stringify } from "yaml";
|
||||
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";
|
||||
export type { RuntimeBindings } from "./bindings.js";
|
||||
|
||||
@@ -86,7 +86,15 @@ export function renderRuntimeConfig(
|
||||
identity?: RuntimeIdentity,
|
||||
installation: RuntimeInstallationOverlay = {},
|
||||
): 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) {
|
||||
throw new Error("runtime configuration requires complete bindings");
|
||||
}
|
||||
|
||||
@@ -102,11 +102,6 @@ interface QdrantVectorStore {
|
||||
collection: string;
|
||||
dimensions: 1024;
|
||||
distance: "cosine";
|
||||
database?: string;
|
||||
schema?: string;
|
||||
port?: number;
|
||||
timeout_ms?: number;
|
||||
supported_transports?: VectorTransport[];
|
||||
}
|
||||
|
||||
export interface WorkspaceV3 extends WorkspaceBase<3, QdrantVectorStore> {}
|
||||
|
||||
@@ -21,18 +21,12 @@ export interface QdrantVectorStore {
|
||||
collection: string;
|
||||
dimensions: 1024;
|
||||
distance: "cosine";
|
||||
database?: string;
|
||||
schema?: string;
|
||||
port?: number;
|
||||
timeout_ms?: number;
|
||||
supported_transports?: ("pgvector_direct" | "rest_api" | "ssh_tunnel")[];
|
||||
}
|
||||
|
||||
export interface InternalEmbedding {
|
||||
provider: "ollama_internal";
|
||||
model: "qwen3-embedding:0.6b";
|
||||
dimensions: 1024;
|
||||
timeout_ms?: number;
|
||||
}
|
||||
|
||||
export interface WorkspaceV2 {
|
||||
@@ -113,5 +107,5 @@ export interface WorkspaceV3 {
|
||||
embedding: InternalEmbedding;
|
||||
};
|
||||
llm_policy: WorkspaceV2["llm_policy"];
|
||||
diagnostics?: WorkspaceV2["diagnostics"];
|
||||
diagnostics?: Pick<NonNullable<WorkspaceV2["diagnostics"]>, "dwh_rest">;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user