fix: complete workspace diagnostic adapters

This commit is contained in:
2026-08-04 00:05:33 +02:00
parent ca97bbb9c2
commit 67bb4f6ef9
2 changed files with 111 additions and 14 deletions
+49 -14
View File
@@ -1,7 +1,9 @@
import { randomUUID } from "node:crypto";
import { readFile, realpath } from "node:fs/promises";
import { createConnection } from "node:net";
import { createServer } from "node:net";
import { once } from "node:events";
import { spawn } from "node:child_process";
import { Client } from "pg";
import { MAX_WORKSPACE_DIAGNOSTIC_TIMEOUT_MS } from "../config.js";
import { buildInstallationContract } from "./contracts.js";
@@ -127,6 +129,7 @@ export interface WriteDiagnosticRecordRequest {
timeoutMs: number;
signal: AbortSignal;
credentialFile?: string;
tlsCaFile?: string;
baseUrl?: string;
diagnostic?: RestDiagnosticRequest;
}
@@ -157,6 +160,8 @@ export interface ConcreteDiagnosticAdapterDependencies {
directProtocol?: DirectProtocolFactory;
sshProcess?: SshProcessFactory;
databaseClient?: DatabaseDiagnosticClientFactory;
sshSpawn?: (args: readonly string[]) => { kill(signal?: NodeJS.Signals): boolean };
reserveLoopbackPort?: () => Promise<number>;
}
/**
@@ -211,6 +216,21 @@ async function restHeaders(
return diagnostic.auth === "bearer" ? { authorization: `Bearer ${secret}` } : { "x-api-key": secret };
}
async function reserveLoopbackPort(): Promise<number> {
const server = createServer();
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
try {
const address = server.address();
if (!address || typeof address === "string") throw new Error("SSH tunnel port unavailable");
return address.port;
} finally {
await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
}
/**
* Concrete production adapters deliberately retain only probe metadata. Protocol failures and
* response bodies are discarded at this boundary; callers receive fixed diagnostics instead.
@@ -218,6 +238,22 @@ async function restHeaders(
export function createConcreteDiagnosticAdapters(
dependencies: ConcreteDiagnosticAdapterDependencies = {},
): DiagnosticAdapters {
const spawnSsh = dependencies.sshSpawn ?? ((args: readonly string[]) => spawn("ssh", [...args], { stdio: "ignore" }));
const reserveSshPort = dependencies.reserveLoopbackPort ?? reserveLoopbackPort;
const sshProcess = dependencies.sshProcess ?? {
async start(request: SshTunnelRequest, args: readonly string[]) {
const port = await reserveSshPort();
const resolvedArgs = args.map((argument) => argument === `127.0.0.1:0:${request.targetHost}:${request.targetPort}`
? `127.0.0.1:${port}:${request.targetHost}:${request.targetPort}` : argument);
const child = spawnSsh(resolvedArgs);
const abort = () => { child.kill("SIGTERM"); };
request.signal.addEventListener("abort", abort, { once: true });
return {
tunnel: { host: "127.0.0.1" as const, port },
async close() { request.signal.removeEventListener("abort", abort); child.kill("SIGTERM"); },
};
},
};
const databaseClient = dependencies.databaseClient ?? {
async connect(request: { host: string; port: number; database: string; user: string; credentialFile: string; tlsCaFile: string; signal: AbortSignal }) {
const client = new Client({
@@ -295,14 +331,13 @@ export function createConcreteDiagnosticAdapters(
if (!request.knownHostsFile || !(await secretPresent(request.privateKeyFile))) {
throw new Error("SSH probe failed");
}
if (!dependencies.sshProcess) throw new Error("SSH tunnel process is unavailable");
const args = [
"-N", "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=yes",
"-o", `UserKnownHostsFile=${request.knownHostsFile}`, "-i", request.privateKeyFile,
"-p", String(request.sshPort), "-L", `127.0.0.1:0:${request.targetHost}:${request.targetPort}`,
`${request.sshUser}@${request.sshHost}`,
];
const tunnel = await dependencies.sshProcess.start(request, args);
const tunnel = await sshProcess.start(request, args);
try {
return await probe(tunnel.tunnel);
} finally {
@@ -322,7 +357,7 @@ export function createConcreteDiagnosticAdapters(
});
try {
const metadata = await client.query(
"SELECT a.atttypmod - 4 AS dimensions, CASE WHEN pg_get_indexdef(i.indexrelid) LIKE '%vector_cosine_ops%' THEN 'cosine' WHEN pg_get_indexdef(i.indexrelid) LIKE '%vector_l2_ops%' THEN 'l2' WHEN pg_get_indexdef(i.indexrelid) LIKE '%vector_ip_ops%' THEN 'inner_product' END AS distance FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid JOIN pg_namespace n ON n.oid = c.relnamespace LEFT JOIN pg_index i ON i.indrelid = c.oid WHERE n.nspname = $1 AND c.relname = $2 AND a.attnum > 0 AND NOT a.attisdropped AND a.atttypid = (SELECT oid FROM pg_type WHERE typname = 'vector') LIMIT 1",
"SELECT a.atttypmod - 4 AS dimensions, CASE WHEN pg_get_indexdef(i.indexrelid) LIKE '%vector_cosine_ops%' THEN 'cosine' WHEN pg_get_indexdef(i.indexrelid) LIKE '%vector_l2_ops%' THEN 'l2' WHEN pg_get_indexdef(i.indexrelid) LIKE '%vector_ip_ops%' THEN 'inner_product' END AS distance FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid JOIN pg_namespace n ON n.oid = c.relnamespace JOIN pg_index i ON i.indrelid = c.oid AND a.attnum = ANY(i.indkey) WHERE n.nspname = $1 AND c.relname = $2 AND a.attnum > 0 AND NOT a.attisdropped AND a.atttypid = (SELECT oid FROM pg_type WHERE typname = 'vector') ORDER BY i.indexrelid LIMIT 1",
[resource.schema, request.collection],
);
const row = metadata.rows[0];
@@ -332,11 +367,12 @@ export function createConcreteDiagnosticAdapters(
await client.end().catch(() => undefined);
}
}
if (request.transport !== "rest_api" || !request.baseUrl || !request.credentialFile || !request.diagnostic
|| !(await secretPresent(request.credentialFile))) throw new Error("vector metadata adapter is unavailable");
if (request.transport !== "rest_api" || !request.baseUrl || !request.diagnostic || request.tlsCaFile) {
throw new Error("vector metadata adapter is unavailable");
}
const response = await fetch(resolveDiagnosticUrl(request.baseUrl, request.diagnostic.path).toString(), {
method: request.diagnostic.method,
headers: { authorization: `Bearer ${(await readFile(request.credentialFile, "utf8")).trim()}` },
headers: await restHeaders(request.diagnostic, request.credentialFile),
signal: request.signal,
redirect: "error",
});
@@ -353,10 +389,10 @@ export function createConcreteDiagnosticAdapters(
};
},
async probeEmbedding(request) {
if (!request.diagnostic || !(await secretPresent(request.credentialFile ?? ""))) throw new Error("embedding probe failed");
if (!request.diagnostic || request.tlsCaFile) throw new Error("embedding probe failed");
const response = await fetch(resolveDiagnosticUrl(request.baseUrl, request.diagnostic.path).toString(), {
method: request.diagnostic.method,
headers: { authorization: `Bearer ${await readFile(request.credentialFile!, "utf8")}` },
headers: await restHeaders(request.diagnostic, request.credentialFile),
signal: request.signal,
redirect: "error",
});
@@ -366,12 +402,11 @@ export function createConcreteDiagnosticAdapters(
return { available: true, dimensions: payload[request.diagnostic.response.dimensions] as number };
},
async writeDiagnosticRecord(request) {
if (!request.baseUrl || !request.credentialFile || !request.diagnostic
|| !(await secretPresent(request.credentialFile))) throw new Error("vector write adapter is unavailable");
if (!request.baseUrl || !request.diagnostic || request.tlsCaFile) throw new Error("vector write adapter is unavailable");
const response = await fetch(resolveDiagnosticUrl(request.baseUrl, request.diagnostic.path).toString(), {
method: request.diagnostic.method,
headers: {
authorization: `Bearer ${(await readFile(request.credentialFile, "utf8")).trim()}`,
...await restHeaders(request.diagnostic, request.credentialFile),
"content-type": "application/json",
},
body: JSON.stringify({ operation: "create", id: request.id, collection: request.collection, dimensions: request.dimensions }),
@@ -381,12 +416,11 @@ export function createConcreteDiagnosticAdapters(
if (!response.ok) throw new Error("vector write adapter is unavailable");
},
async removeDiagnosticRecord(request) {
if (!request.baseUrl || !request.credentialFile || !request.diagnostic
|| !(await secretPresent(request.credentialFile))) throw new Error("vector write adapter is unavailable");
if (!request.baseUrl || !request.diagnostic || request.tlsCaFile) throw new Error("vector write adapter is unavailable");
const response = await fetch(resolveDiagnosticUrl(request.baseUrl, request.diagnostic.path).toString(), {
method: request.diagnostic.method,
headers: {
authorization: `Bearer ${(await readFile(request.credentialFile, "utf8")).trim()}`,
...await restHeaders(request.diagnostic, request.credentialFile),
"content-type": "application/json",
},
body: JSON.stringify({ operation: "remove", id: request.id, collection: request.collection }),
@@ -728,6 +762,7 @@ export function createWorkspaceDiagnoser(
timeoutMs: vectorTimeout,
signal: new AbortController().signal,
credentialFile,
tlsCaFile: bindings.vector.values[bindingName(canonical, "VECTOR", "TLS_CA_FILE")],
baseUrl: bindings.vector.values[bindingName(canonical, "VECTOR", "BASE_URL")],
diagnostic: canonical.diagnostics.vector_rest.reversible_probe,
};