feat: define workspace diagnostic contracts
This commit is contained in:
@@ -63,7 +63,7 @@ function isSafeSecretFile(path: string, secretRoots: readonly string[]): boolean
|
||||
}
|
||||
|
||||
function requiredSuffixes(
|
||||
role: InstallationRole,
|
||||
role: Exclude<InstallationRole, "VECTOR_WRITER">,
|
||||
transport: DwhTransport | VectorTransport,
|
||||
): readonly InstallationSuffix[] {
|
||||
if (role === "EMBEDDING") return EMBEDDING_REQUIRED_SUFFIXES;
|
||||
@@ -76,7 +76,7 @@ function requiredSuffixes(
|
||||
*/
|
||||
export function resolveBinding(
|
||||
workspace: CanonicalWorkspace,
|
||||
role: InstallationRole,
|
||||
role: Exclude<InstallationRole, "VECTOR_WRITER">,
|
||||
env: NodeJS.ProcessEnv,
|
||||
secretRoots: readonly string[],
|
||||
): ResolvedBinding {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { validateCanonicalWorkspace } from "./schema.js";
|
||||
import type { CanonicalWorkspace, DwhTransport, VectorTransport } from "./schema.js";
|
||||
|
||||
export type InstallationRole = "DWH" | "VECTOR" | "EMBEDDING";
|
||||
export type InstallationRole = "DWH" | "VECTOR" | "VECTOR_WRITER" | "EMBEDDING";
|
||||
export type InstallationSuffix =
|
||||
| "TRANSPORT"
|
||||
| "HOST"
|
||||
@@ -133,6 +133,9 @@ export function buildInstallationContract(workspace: CanonicalWorkspace): Instal
|
||||
"VECTOR",
|
||||
canonical.semantic_index.vector_store.supported_transports,
|
||||
),
|
||||
...(canonical.semantic_index.vector_writer
|
||||
? [createVariable(namespace, "VECTOR_WRITER", "API_KEY_FILE")]
|
||||
: []),
|
||||
...EMBEDDING_SUFFIXES.map((suffix) => createVariable(namespace, "EMBEDDING", suffix)),
|
||||
],
|
||||
};
|
||||
@@ -170,8 +173,10 @@ export function renderWorkspaceDocs(workspace: CanonicalWorkspace): { envExample
|
||||
"",
|
||||
"Use the following UI fields as installation bindings. Secret fields always contain file paths, never secret values.",
|
||||
"",
|
||||
...(["DWH", "VECTOR", "EMBEDDING"] as const).flatMap((role) => [
|
||||
`## ${role === "DWH" ? "Data warehouse" : role === "VECTOR" ? "Vector store" : "Embedding service"}`,
|
||||
...(["DWH", "VECTOR", "VECTOR_WRITER", "EMBEDDING"] as const)
|
||||
.filter((role) => variablesByRole.has(role))
|
||||
.flatMap((role) => [
|
||||
`## ${role === "DWH" ? "Data warehouse" : role === "VECTOR" ? "Vector store" : role === "VECTOR_WRITER" ? "Vector writer" : "Embedding service"}`,
|
||||
"",
|
||||
...(variablesByRole.get(role) ?? []).map((variable) => (
|
||||
`- \`${variable.name}\`${variable.transports ? ` (for: ${variable.transports.join(", ")})` : ""}`
|
||||
|
||||
@@ -90,7 +90,10 @@ export function renderRuntimeConfig(
|
||||
return variable.name;
|
||||
};
|
||||
const dwhIdentity = { database: canonical.dwh.database, schema: canonical.dwh.schema };
|
||||
const vectorIdentity = dwhIdentity;
|
||||
const vectorIdentity = {
|
||||
database: canonical.semantic_index.vector_store.database,
|
||||
schema: canonical.semantic_index.vector_store.schema,
|
||||
};
|
||||
const dwhDirect = bindings.dwh.transport === "postgres_direct";
|
||||
const vectorDirect = bindings.vector.transport === "pgvector_direct";
|
||||
const database = dwhDirect
|
||||
|
||||
@@ -7,6 +7,33 @@ export type DwhTransport = (typeof DWH_TRANSPORTS)[number];
|
||||
export const VECTOR_TRANSPORTS = ["pgvector_direct", "rest_api", "ssh_tunnel"] as const;
|
||||
export type VectorTransport = (typeof VECTOR_TRANSPORTS)[number];
|
||||
|
||||
export const REST_DIAGNOSTIC_METHODS = ["GET", "POST"] as const;
|
||||
export type RestDiagnosticMethod = (typeof REST_DIAGNOSTIC_METHODS)[number];
|
||||
|
||||
export const DIAGNOSTIC_AUTH_MODES = ["none", "bearer"] as const;
|
||||
export type DiagnosticAuthMode = (typeof DIAGNOSTIC_AUTH_MODES)[number];
|
||||
|
||||
export interface RestDiagnosticRequest {
|
||||
method: RestDiagnosticMethod;
|
||||
path: string;
|
||||
auth: DiagnosticAuthMode;
|
||||
}
|
||||
|
||||
export interface CanonicalDiagnostics {
|
||||
dwh_rest?: RestDiagnosticRequest & {
|
||||
response: { database: string; schema: string };
|
||||
};
|
||||
vector_rest?: {
|
||||
metadata: RestDiagnosticRequest & {
|
||||
response: { collection: string; dimensions: string; distance: string };
|
||||
};
|
||||
reversible_probe?: RestDiagnosticRequest & { method: "POST" };
|
||||
};
|
||||
embedding?: RestDiagnosticRequest & {
|
||||
response: { model: string; dimensions: string };
|
||||
};
|
||||
}
|
||||
|
||||
export interface CanonicalWorkspace {
|
||||
workspace: {
|
||||
schema_version: 1;
|
||||
@@ -26,6 +53,8 @@ export interface CanonicalWorkspace {
|
||||
semantic_index: {
|
||||
vector_store: {
|
||||
engine: "pgvector";
|
||||
database: string;
|
||||
schema: string;
|
||||
collection: string;
|
||||
dimensions: number;
|
||||
distance: "cosine" | "l2" | "inner_product";
|
||||
@@ -33,6 +62,7 @@ export interface CanonicalWorkspace {
|
||||
timeout_ms?: number;
|
||||
supported_transports: VectorTransport[];
|
||||
};
|
||||
vector_writer?: Record<string, never>;
|
||||
embedding: {
|
||||
provider: "ollama_compatible" | "openai_compatible";
|
||||
model: string;
|
||||
@@ -44,6 +74,7 @@ export interface CanonicalWorkspace {
|
||||
default?: `${string}/${string}`;
|
||||
allowed: `${string}/${string}`[];
|
||||
};
|
||||
diagnostics?: CanonicalDiagnostics;
|
||||
}
|
||||
|
||||
const workspaceId = z.string().regex(/^[a-z][a-z0-9-]{2,62}$/, {
|
||||
@@ -58,6 +89,31 @@ const timeoutMs = z.number().int().positive();
|
||||
const modelReference = z.string().regex(/^[^/\s]+\/[^/\s]+$/, {
|
||||
message: "model must use provider/model syntax",
|
||||
});
|
||||
const diagnosticPath = z.string().regex(/^\/[^\s?#]*$/, {
|
||||
message: "diagnostic paths must be absolute and cannot include whitespace, queries, or fragments",
|
||||
});
|
||||
const responseField = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/, {
|
||||
message: "diagnostic response fields must be identifiers",
|
||||
});
|
||||
const restDiagnosticRequest = z.object({
|
||||
method: z.enum(REST_DIAGNOSTIC_METHODS),
|
||||
path: diagnosticPath,
|
||||
auth: z.enum(DIAGNOSTIC_AUTH_MODES),
|
||||
}).strict();
|
||||
const dwhRestDiagnostic = restDiagnosticRequest.extend({
|
||||
response: z.object({ database: responseField, schema: responseField }).strict(),
|
||||
}).strict();
|
||||
const vectorMetadataDiagnostic = restDiagnosticRequest.extend({
|
||||
response: z.object({
|
||||
collection: responseField,
|
||||
dimensions: responseField,
|
||||
distance: responseField,
|
||||
}).strict(),
|
||||
}).strict();
|
||||
const reversibleVectorProbe = restDiagnosticRequest.extend({ method: z.literal("POST") }).strict();
|
||||
const embeddingDiagnostic = restDiagnosticRequest.extend({
|
||||
response: z.object({ model: responseField, dimensions: responseField }).strict(),
|
||||
}).strict();
|
||||
|
||||
function unique<T>(values: readonly T[], context: z.RefinementCtx, path: PropertyKey[]) {
|
||||
if (new Set(values).size !== values.length) {
|
||||
@@ -84,6 +140,8 @@ const WorkspaceSchema = z.object({
|
||||
semantic_index: z.object({
|
||||
vector_store: z.object({
|
||||
engine: z.literal("pgvector"),
|
||||
database: identifier,
|
||||
schema: identifier,
|
||||
collection: identifier,
|
||||
dimensions,
|
||||
distance: z.enum(["cosine", "l2", "inner_product"]),
|
||||
@@ -91,6 +149,7 @@ const WorkspaceSchema = z.object({
|
||||
timeout_ms: timeoutMs.optional(),
|
||||
supported_transports: z.array(z.enum(VECTOR_TRANSPORTS)).min(1),
|
||||
}).strict(),
|
||||
vector_writer: z.object({}).strict().optional(),
|
||||
embedding: z.object({
|
||||
provider: z.enum(["ollama_compatible", "openai_compatible"]),
|
||||
model: z.string().trim().min(1),
|
||||
@@ -102,6 +161,14 @@ const WorkspaceSchema = z.object({
|
||||
default: modelReference.optional(),
|
||||
allowed: z.array(modelReference).min(1),
|
||||
}).strict(),
|
||||
diagnostics: z.object({
|
||||
dwh_rest: dwhRestDiagnostic.optional(),
|
||||
vector_rest: z.object({
|
||||
metadata: vectorMetadataDiagnostic,
|
||||
reversible_probe: reversibleVectorProbe.optional(),
|
||||
}).strict().optional(),
|
||||
embedding: embeddingDiagnostic.optional(),
|
||||
}).strict().optional(),
|
||||
}).strict().superRefine((workspace, context) => {
|
||||
unique(workspace.dwh.supported_transports, context, ["dwh", "supported_transports"]);
|
||||
unique(
|
||||
|
||||
Reference in New Issue
Block a user