fix: align workspace diagnostic contracts
This commit is contained in:
@@ -10,6 +10,13 @@ import {
|
||||
type WorkspaceDescriptor,
|
||||
} from "./schema.js";
|
||||
|
||||
export interface RuntimeBindings {
|
||||
dwh: ResolvedBinding;
|
||||
vector: ResolvedBinding;
|
||||
vectorWriter: ResolvedBinding;
|
||||
embedding: ResolvedBinding;
|
||||
}
|
||||
|
||||
export interface ResolvedBinding {
|
||||
transport: DwhTransport | VectorTransport;
|
||||
values: Record<string, string>;
|
||||
@@ -63,12 +70,19 @@ function isSafeSecretFile(path: string, secretRoots: readonly string[]): boolean
|
||||
}
|
||||
|
||||
function requiredSuffixes(
|
||||
workspace: WorkspaceDescriptor,
|
||||
role: InstallationRole,
|
||||
transport: DwhTransport | VectorTransport,
|
||||
): readonly InstallationSuffix[] {
|
||||
if (role === "EMBEDDING") return EMBEDDING_REQUIRED_SUFFIXES;
|
||||
if (role === "VECTOR_WRITER") return ["API_KEY_FILE"];
|
||||
return REQUIRED_SUFFIXES[role][transport] ?? [];
|
||||
const required = REQUIRED_SUFFIXES[role][transport] ?? [];
|
||||
const diagnostic = role === "DWH"
|
||||
? workspace.diagnostics?.dwh_rest
|
||||
: workspace.diagnostics?.vector_rest?.metadata;
|
||||
return transport === "rest_api" && diagnostic?.auth === "none"
|
||||
? required.filter((suffix) => suffix !== "API_KEY_FILE")
|
||||
: required;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +112,7 @@ export function resolveBinding(
|
||||
missing.push(transportVariable.name);
|
||||
}
|
||||
|
||||
const required = new Set(requiredSuffixes(role, selectedTransport));
|
||||
const required = new Set(requiredSuffixes(canonical, role, selectedTransport));
|
||||
const values: Record<string, string> = {};
|
||||
for (const variable of variables) {
|
||||
if (variable.suffix === "TRANSPORT") continue;
|
||||
@@ -115,3 +129,17 @@ export function resolveBinding(
|
||||
|
||||
return { transport: selectedTransport, values, missing };
|
||||
}
|
||||
|
||||
/** Resolve all runtime roles together so optional writer credentials cannot be smuggled into reader bindings. */
|
||||
export function resolveRuntimeBindings(
|
||||
workspace: WorkspaceDescriptor,
|
||||
env: NodeJS.ProcessEnv,
|
||||
secretRoots: readonly string[],
|
||||
): RuntimeBindings {
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -131,7 +131,9 @@ export interface WriteDiagnosticRecordRequest {
|
||||
credentialFile?: string;
|
||||
tlsCaFile?: string;
|
||||
baseUrl?: string;
|
||||
diagnostic?: RestDiagnosticRequest;
|
||||
diagnostic?: RestDiagnosticRequest & {
|
||||
response: { operation: string };
|
||||
};
|
||||
}
|
||||
|
||||
export interface DirectProtocolFactory {
|
||||
@@ -145,7 +147,7 @@ export interface DatabaseDiagnosticClient {
|
||||
|
||||
export interface DatabaseDiagnosticClientFactory {
|
||||
connect(request: {
|
||||
host: string; port: number; database: string; user: string; credentialFile: string; tlsCaFile: string; signal: AbortSignal;
|
||||
host: string; port: number; database: string; user: string; credentialFile: string; tlsCaFile?: string; signal: AbortSignal;
|
||||
}): Promise<DatabaseDiagnosticClient>;
|
||||
}
|
||||
|
||||
@@ -296,11 +298,13 @@ export function createConcreteDiagnosticAdapters(
|
||||
},
|
||||
};
|
||||
const databaseClient = dependencies.databaseClient ?? {
|
||||
async connect(request: { host: string; port: number; database: string; user: string; credentialFile: string; tlsCaFile: string; signal: AbortSignal }) {
|
||||
async connect(request: { host: string; port: number; database: string; user: string; credentialFile: string; tlsCaFile?: string; signal: AbortSignal }) {
|
||||
const client = new Client({
|
||||
host: request.host, port: request.port, database: request.database, user: request.user,
|
||||
password: (await readFile(request.credentialFile, "utf8")).trim(),
|
||||
ssl: { ca: await readFile(request.tlsCaFile, "utf8"), rejectUnauthorized: true },
|
||||
ssl: request.tlsCaFile
|
||||
? { ca: await readFile(request.tlsCaFile, "utf8"), rejectUnauthorized: true }
|
||||
: { rejectUnauthorized: true },
|
||||
connectionTimeoutMillis: 5_000,
|
||||
});
|
||||
const abort = () => { void client.end(); };
|
||||
@@ -317,7 +321,7 @@ export function createConcreteDiagnosticAdapters(
|
||||
};
|
||||
const directProtocol = dependencies.directProtocol ?? {
|
||||
async probe(request: ConnectorDiagnosticRequest): Promise<ConnectorDiagnosticResult> {
|
||||
if (!request.host || !request.port || !request.user || !request.credentialFile || !request.tlsCaFile
|
||||
if (!request.host || !request.port || !request.user || !request.credentialFile
|
||||
|| !(await secretPresent(request.credentialFile))) {
|
||||
throw new Error("direct probe failed");
|
||||
}
|
||||
@@ -388,7 +392,7 @@ export function createConcreteDiagnosticAdapters(
|
||||
async inspectVector(request) {
|
||||
if (request.transport === "pgvector_direct" || request.transport === "ssh_tunnel") {
|
||||
const resource = request.resource;
|
||||
if (!request.host || !request.port || !request.user || !request.credentialFile || !request.tlsCaFile
|
||||
if (!request.host || !request.port || !request.user || !request.credentialFile
|
||||
|| !resource?.database || !resource.schema || !(await secretPresent(request.credentialFile))) {
|
||||
throw new Error("vector metadata adapter is unavailable");
|
||||
}
|
||||
@@ -454,7 +458,10 @@ export function createConcreteDiagnosticAdapters(
|
||||
signal: request.signal,
|
||||
redirect: "error",
|
||||
});
|
||||
if (!response.ok) throw new Error("vector write adapter is unavailable");
|
||||
const payload = await response.json().catch(() => undefined) as Record<string, unknown> | undefined;
|
||||
if (!response.ok || !payload || payload[request.diagnostic.response.operation] !== "create") {
|
||||
throw new Error("vector write adapter is unavailable");
|
||||
}
|
||||
},
|
||||
async removeDiagnosticRecord(request) {
|
||||
if (!request.baseUrl || !request.diagnostic || request.tlsCaFile) throw new Error("vector write adapter is unavailable");
|
||||
@@ -468,7 +475,10 @@ export function createConcreteDiagnosticAdapters(
|
||||
signal: request.signal,
|
||||
redirect: "error",
|
||||
});
|
||||
if (!response.ok) throw new Error("vector write adapter is unavailable");
|
||||
const payload = await response.json().catch(() => undefined) as Record<string, unknown> | undefined;
|
||||
if (!response.ok || !payload || payload[request.diagnostic.response.operation] !== "remove") {
|
||||
throw new Error("vector write adapter is unavailable");
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -789,7 +799,7 @@ export function createWorkspaceDiagnoser(
|
||||
&& bindings.vector.transport === "rest_api"
|
||||
&& !diagnostics.some((diagnostic) => diagnostic.level === "error")
|
||||
) {
|
||||
const credentialFile = bindings.vector.values[bindingName(canonical, "VECTOR_WRITER", "API_KEY_FILE")];
|
||||
const credentialFile = bindings.vectorWriter.values[bindingName(canonical, "VECTOR_WRITER", "API_KEY_FILE")];
|
||||
if (!credentialFile) return { activatable: true, diagnostics };
|
||||
const readerCredentialFile = bindings.vector.values[bindingName(canonical, "VECTOR", "API_KEY_FILE")];
|
||||
if (readerCredentialFile && await sameSecretFile(credentialFile, readerCredentialFile)) {
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { stringify } from "yaml";
|
||||
import { buildInstallationContract } from "./contracts.js";
|
||||
import { validateCanonicalWorkspace, type WorkspaceDescriptor } from "./schema.js";
|
||||
import type { ResolvedBinding } from "./bindings.js";
|
||||
|
||||
export interface RuntimeBindings {
|
||||
dwh: ResolvedBinding;
|
||||
vector: ResolvedBinding;
|
||||
embedding: ResolvedBinding;
|
||||
}
|
||||
import type { ResolvedBinding, RuntimeBindings } from "./bindings.js";
|
||||
export type { RuntimeBindings } from "./bindings.js";
|
||||
|
||||
export interface RuntimePaths {
|
||||
sessions: string;
|
||||
|
||||
@@ -25,7 +25,11 @@ export interface CanonicalDiagnostics {
|
||||
metadata: RestDiagnosticRequest & {
|
||||
response: { collection: string; dimensions: string; distance: string };
|
||||
};
|
||||
reversible_probe?: RestDiagnosticRequest & { method: "POST" };
|
||||
reversible_probe?: RestDiagnosticRequest & {
|
||||
method: "POST";
|
||||
auth: Exclude<DiagnosticAuthMode, "none">;
|
||||
response: { operation: string };
|
||||
};
|
||||
};
|
||||
embedding?: RestDiagnosticRequest & { response: { model: string; dimensions: string } };
|
||||
}
|
||||
@@ -124,7 +128,11 @@ const vectorMetadataDiagnostic = restDiagnosticRequest.extend({
|
||||
distance: responseField,
|
||||
}).strict(),
|
||||
}).strict();
|
||||
const reversibleVectorProbe = restDiagnosticRequest.extend({ method: z.literal("POST") }).strict();
|
||||
const reversibleVectorProbe = restDiagnosticRequest.extend({
|
||||
method: z.literal("POST"),
|
||||
auth: z.enum(["bearer", "x-api-key"]),
|
||||
response: z.object({ operation: responseField }).strict(),
|
||||
}).strict();
|
||||
const embeddingDiagnostic = restDiagnosticRequest.extend({
|
||||
response: z.object({ model: responseField, dimensions: responseField }).strict(),
|
||||
}).strict();
|
||||
|
||||
@@ -82,6 +82,7 @@ const directBindings: RuntimeBindings = {
|
||||
THT_WS_PSD_CLINICAL_VECTOR_TLS_CA_FILE: "/run/secrets/vector-ca.pem",
|
||||
},
|
||||
},
|
||||
vectorWriter: { transport: "rest_api", missing: [], values: {} },
|
||||
embedding: {
|
||||
transport: "rest_api",
|
||||
missing: [],
|
||||
|
||||
@@ -2,7 +2,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, expect, test } from "vitest";
|
||||
import { resolveBinding } from "../src/workspaces/bindings.js";
|
||||
import { resolveBinding, resolveRuntimeBindings } from "../src/workspaces/bindings.js";
|
||||
import { parseWorkspaceYaml } from "../src/workspaces/schema.js";
|
||||
|
||||
const workspace = parseWorkspaceYaml(`workspace:
|
||||
@@ -56,6 +56,64 @@ test("marks a portable workspace non-activatable when its local REST key file is
|
||||
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", {
|
||||
|
||||
@@ -147,6 +147,7 @@ llm_policy:
|
||||
THT_WS_PSD_CLINICAL_VECTOR_PASSWORD_FILE: "/run/secrets/vector-reader",
|
||||
},
|
||||
},
|
||||
vectorWriter: { transport: "rest_api", missing: [], values: {} },
|
||||
embedding: {
|
||||
transport: "rest_api",
|
||||
missing: [],
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
createWorkspaceDiagnoser,
|
||||
type DiagnosticAdapters,
|
||||
} from "../src/workspaces/diagnostics.js";
|
||||
import { resolveRuntimeBindings } from "../src/workspaces/bindings.js";
|
||||
import type { RuntimeBindings } from "../src/workspaces/runtime-renderer.js";
|
||||
import { parseWorkspaceYaml } from "../src/workspaces/schema.js";
|
||||
|
||||
@@ -56,6 +57,7 @@ diagnostics:
|
||||
method: POST
|
||||
path: /vector/diagnostic-probe
|
||||
auth: bearer
|
||||
response: { operation: operation }
|
||||
`);
|
||||
|
||||
const writerWorkspace = parseWorkspaceYaml(`workspace:
|
||||
@@ -88,6 +90,11 @@ semantic_index:
|
||||
llm_policy:
|
||||
allowed: [zai/glm-5.2]
|
||||
diagnostics:
|
||||
dwh_rest:
|
||||
method: POST
|
||||
path: /rpc/ping
|
||||
auth: bearer
|
||||
response: { database: database, schema: schema }
|
||||
vector_rest:
|
||||
metadata:
|
||||
method: GET
|
||||
@@ -98,6 +105,7 @@ diagnostics:
|
||||
method: POST
|
||||
path: /vector/diagnostic-probe
|
||||
auth: bearer
|
||||
response: { operation: operation }
|
||||
`);
|
||||
|
||||
const bindings: RuntimeBindings = {
|
||||
@@ -123,6 +131,7 @@ const bindings: RuntimeBindings = {
|
||||
THT_WS_PSD_CLINICAL_VECTOR_TLS_CA_FILE: "/run/secrets/vector-ca",
|
||||
},
|
||||
},
|
||||
vectorWriter: { transport: "rest_api", missing: [], values: {} },
|
||||
embedding: {
|
||||
transport: "rest_api",
|
||||
missing: [],
|
||||
@@ -142,9 +151,13 @@ const writerBindings: RuntimeBindings = {
|
||||
values: {
|
||||
THT_WS_PSD_CLINICAL_VECTOR_BASE_URL: "https://vector.example.test",
|
||||
THT_WS_PSD_CLINICAL_VECTOR_API_KEY_FILE: "/run/secrets/vector-reader-key",
|
||||
THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE: "/run/secrets/vector-writer-key",
|
||||
},
|
||||
},
|
||||
vectorWriter: {
|
||||
transport: "rest_api",
|
||||
missing: [],
|
||||
values: { THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE: "/run/secrets/vector-writer-key" },
|
||||
},
|
||||
};
|
||||
|
||||
function successfulAdapters(overrides: Partial<DiagnosticAdapters> = {}): DiagnosticAdapters {
|
||||
@@ -448,6 +461,40 @@ test("requires a matching embedding model vector and removes its unique write pr
|
||||
}));
|
||||
});
|
||||
|
||||
test("passes the resolver's distinct vector-writer binding to the diagnoser", async () => {
|
||||
const directory = await mkdtemp(join(tmpdir(), "thothii-diagnostic-bindings-"));
|
||||
const readerKey = join(directory, "reader-key");
|
||||
const writerKey = join(directory, "writer-key");
|
||||
const dwhKey = join(directory, "dwh-key");
|
||||
await Promise.all([
|
||||
writeFile(readerKey, "reader\n", { mode: 0o600 }),
|
||||
writeFile(writerKey, "writer\n", { mode: 0o600 }),
|
||||
writeFile(dwhKey, "dwh\n", { mode: 0o600 }),
|
||||
]);
|
||||
const adapters = successfulAdapters();
|
||||
try {
|
||||
const resolved = resolveRuntimeBindings(writerWorkspace, {
|
||||
THT_WS_PSD_CLINICAL_DWH_TRANSPORT: "rest_api",
|
||||
THT_WS_PSD_CLINICAL_DWH_BASE_URL: "https://dwh.example.test",
|
||||
THT_WS_PSD_CLINICAL_DWH_API_KEY_FILE: dwhKey,
|
||||
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: readerKey,
|
||||
THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE: writerKey,
|
||||
THT_WS_PSD_CLINICAL_EMBEDDING_BASE_URL: "https://embedding.example.test",
|
||||
}, [directory]);
|
||||
|
||||
await diagnose(adapters)(writerWorkspace, resolved, { writeProbe: true });
|
||||
|
||||
expect(resolved.vectorWriter.values).toEqual({
|
||||
THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE: writerKey,
|
||||
});
|
||||
expect(adapters.writeDiagnosticRecord).toHaveBeenCalledWith(expect.objectContaining({ credentialFile: writerKey }));
|
||||
} finally {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("keeps a reader-only workspace activatable without a vector write probe", async () => {
|
||||
const adapters = successfulAdapters();
|
||||
|
||||
@@ -476,11 +523,8 @@ test("rejects a writer credential that aliases the reader credential", async ()
|
||||
const adapters = successfulAdapters();
|
||||
const aliasedBindings: RuntimeBindings = {
|
||||
...writerBindings,
|
||||
vector: { ...writerBindings.vector, values: {
|
||||
...writerBindings.vector.values,
|
||||
THT_WS_PSD_CLINICAL_VECTOR_API_KEY_FILE: readerKey,
|
||||
THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE: writerAlias,
|
||||
} },
|
||||
vector: { ...writerBindings.vector, values: { ...writerBindings.vector.values, THT_WS_PSD_CLINICAL_VECTOR_API_KEY_FILE: readerKey } },
|
||||
vectorWriter: { ...writerBindings.vectorWriter, values: { THT_WS_PSD_CLINICAL_VECTOR_WRITER_API_KEY_FILE: writerAlias } },
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -555,6 +599,29 @@ test("requires an authenticated TLS database query before direct diagnostics suc
|
||||
}
|
||||
});
|
||||
|
||||
test("uses system trust for direct and SSH PostgreSQL diagnostics when no CA binding exists", async () => {
|
||||
const directory = await mkdtemp(join(tmpdir(), "thothii-diagnostic-"));
|
||||
const passwordFile = join(directory, "password");
|
||||
await writeFile(passwordFile, "password\n", { mode: 0o600 });
|
||||
const query = vi.fn(async () => ({ rows: [{ database: "warehouse", schema: "datawarehouse" }] }));
|
||||
const connect = vi.fn(async () => ({ query, end: vi.fn(async () => undefined) }));
|
||||
const adapter = createConcreteDiagnosticAdapters({ databaseClient: { connect } } as any);
|
||||
try {
|
||||
for (const transport of ["postgres_direct", "ssh_tunnel"] as const) {
|
||||
await expect(adapter.probeConnector({
|
||||
role: "dwh", transport, host: "127.0.0.1", port: 5432, user: "reader", credentialFile: passwordFile,
|
||||
resource: { database: "warehouse", schema: "datawarehouse" }, timeoutMs: 5000,
|
||||
signal: new AbortController().signal,
|
||||
})).resolves.toMatchObject({ tlsVerified: true, authenticated: true });
|
||||
}
|
||||
expect(connect).toHaveBeenCalledTimes(2);
|
||||
expect(connect).toHaveBeenNthCalledWith(1, expect.objectContaining({ tlsCaFile: undefined }));
|
||||
expect(connect).toHaveBeenNthCalledWith(2, expect.objectContaining({ tlsCaFile: undefined }));
|
||||
} finally {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("selects the vector index containing the declared vector column for direct metadata", async () => {
|
||||
const directory = await mkdtemp(join(tmpdir(), "thothii-diagnostic-"));
|
||||
const passwordFile = join(directory, "password");
|
||||
@@ -597,20 +664,41 @@ test("applies declared auth modes and rejects private CA files across vector RES
|
||||
const keyFile = join(directory, "api-key");
|
||||
const caFile = join(directory, "ca.pem");
|
||||
await Promise.all([writeFile(keyFile, "writer-key\n", { mode: 0o600 }), writeFile(caFile, "private-ca\n")]);
|
||||
const fetchSpy = vi.fn(async () => new Response(JSON.stringify({ collection: "clinical_documents", dimensions: 768, distance: "cosine", model: "embed" }), { status: 200, headers: { "content-type": "application/json" } }));
|
||||
const fetchSpy = vi.fn(async () => new Response(JSON.stringify({ collection: "clinical_documents", dimensions: 768, distance: "cosine", model: "embed", operation: "create" }), { status: 200, headers: { "content-type": "application/json" } }));
|
||||
vi.stubGlobal("fetch", fetchSpy);
|
||||
const adapter = createConcreteDiagnosticAdapters();
|
||||
const signal = new AbortController().signal;
|
||||
try {
|
||||
await adapter.inspectVector({ transport: "rest_api", baseUrl: "https://vector.example.test", collection: "clinical_documents", timeoutMs: 1, signal, diagnostic: { method: "GET", path: "/metadata", auth: "none", response: { collection: "collection", dimensions: "dimensions", distance: "distance" } } });
|
||||
await adapter.probeEmbedding({ baseUrl: "https://embed.example.test", model: "embed", timeoutMs: 1, signal, credentialFile: keyFile, diagnostic: { method: "POST", path: "/embed", auth: "x-api-key", response: { model: "model", dimensions: "dimensions" } } });
|
||||
await adapter.writeDiagnosticRecord({ baseUrl: "https://vector.example.test", credentialFile: keyFile, collection: "clinical_documents", dimensions: 768, id: "diagnostic:test", timeoutMs: 1, signal, diagnostic: { method: "POST", path: "/probe", auth: "none" } });
|
||||
expect(fetchSpy.mock.calls[0]?.[1]).toMatchObject({ headers: {} });
|
||||
expect(fetchSpy.mock.calls[1]?.[1]).toMatchObject({ headers: { "x-api-key": "writer-key" } });
|
||||
expect(fetchSpy.mock.calls[2]?.[1]).toMatchObject({ headers: expect.not.objectContaining({ authorization: expect.anything() }) });
|
||||
await expect(adapter.inspectVector({ transport: "rest_api", baseUrl: "https://vector.example.test", credentialFile: keyFile, tlsCaFile: caFile, collection: "clinical_documents", timeoutMs: 1, signal, diagnostic: { method: "GET", path: "/metadata", auth: "bearer", response: { collection: "collection", dimensions: "dimensions", distance: "distance" } } })).rejects.toThrow("vector metadata adapter is unavailable");
|
||||
await expect(adapter.probeEmbedding({ baseUrl: "https://embed.example.test", credentialFile: keyFile, tlsCaFile: caFile, model: "embed", timeoutMs: 1, signal, diagnostic: { method: "POST", path: "/embed", auth: "bearer", response: { model: "model", dimensions: "dimensions" } } })).rejects.toThrow("embedding probe failed");
|
||||
await expect(adapter.removeDiagnosticRecord({ baseUrl: "https://vector.example.test", credentialFile: keyFile, tlsCaFile: caFile, collection: "clinical_documents", id: "diagnostic:test", dimensions: 768, timeoutMs: 1, signal, diagnostic: { method: "POST", path: "/probe", auth: "bearer" } })).rejects.toThrow("vector write adapter is unavailable");
|
||||
await expect(adapter.removeDiagnosticRecord({ baseUrl: "https://vector.example.test", credentialFile: keyFile, tlsCaFile: caFile, collection: "clinical_documents", id: "diagnostic:test", dimensions: 768, timeoutMs: 1, signal, diagnostic: { method: "POST", path: "/probe", auth: "bearer", response: { operation: "operation" } } })).rejects.toThrow("vector write adapter is unavailable");
|
||||
} finally {
|
||||
vi.unstubAllGlobals();
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("validates that the reversible writer response confirms each requested operation", async () => {
|
||||
const directory = await mkdtemp(join(tmpdir(), "thothii-diagnostic-"));
|
||||
const keyFile = join(directory, "writer-key");
|
||||
await writeFile(keyFile, "writer\n", { mode: 0o600 });
|
||||
const fetchSpy = vi.fn(async (_url: string, init: RequestInit) => new Response(JSON.stringify({
|
||||
operation: JSON.parse(String(init.body)).operation === "create" ? "create" : "not-removed",
|
||||
}), { status: 200, headers: { "content-type": "application/json" } }));
|
||||
vi.stubGlobal("fetch", fetchSpy);
|
||||
const request = {
|
||||
baseUrl: "https://vector.example.test", credentialFile: keyFile, collection: "clinical_documents",
|
||||
dimensions: 768, id: "diagnostic:test", timeoutMs: 5000, signal: new AbortController().signal,
|
||||
diagnostic: { method: "POST" as const, path: "/probe", auth: "bearer" as const, response: { operation: "operation" } },
|
||||
};
|
||||
try {
|
||||
const adapter = createConcreteDiagnosticAdapters();
|
||||
await expect(adapter.writeDiagnosticRecord(request)).resolves.toBeUndefined();
|
||||
await expect(adapter.removeDiagnosticRecord(request)).rejects.toThrow("vector write adapter is unavailable");
|
||||
} finally {
|
||||
vi.unstubAllGlobals();
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
|
||||
@@ -98,6 +98,7 @@ test("requires explicit vector database and schema identities with strict diagno
|
||||
+ " method: POST\n"
|
||||
+ " path: /rpc/diagnostic_vector_probe\n"
|
||||
+ " auth: bearer\n"
|
||||
+ " response: { operation: operation }\n"
|
||||
+ " embedding:\n"
|
||||
+ " method: GET\n"
|
||||
+ " path: /models\n"
|
||||
@@ -134,6 +135,24 @@ test("requires explicit vector database and schema identities with strict diagno
|
||||
.toThrow(/response field/i);
|
||||
});
|
||||
|
||||
test("requires a reversible writer probe to declare the response operation it verifies", () => {
|
||||
const writerProbe = validYaml.replace("llm_policy:\n", `diagnostics:
|
||||
vector_rest:
|
||||
metadata:
|
||||
method: GET
|
||||
path: /metadata
|
||||
auth: bearer
|
||||
response: { collection: collection, dimensions: dimensions, distance: distance }
|
||||
reversible_probe:
|
||||
method: POST
|
||||
path: /diagnostic-probe
|
||||
auth: bearer
|
||||
llm_policy:
|
||||
`);
|
||||
|
||||
expect(() => parseWorkspaceYaml(writerProbe)).toThrow(/response|operation/i);
|
||||
});
|
||||
|
||||
test("keeps v1 descriptors readable but requires explicit migration before v2 operations", () => {
|
||||
const v1WithoutVectorIdentity = validYaml.replace("schema_version: 2", "schema_version: 1").replace(
|
||||
" database: postgres\n schema: vectors\n", "",
|
||||
|
||||
Reference in New Issue
Block a user