fix: close internal semantic review gaps
This commit is contained in:
@@ -15,7 +15,11 @@ import {
|
||||
type RuntimePaths,
|
||||
type SemanticRuntimeConfig,
|
||||
} from "../workspaces/runtime-renderer.js";
|
||||
import { parseWorkspaceYaml } from "../workspaces/schema.js";
|
||||
import {
|
||||
parseWorkspaceYaml,
|
||||
validateOperationalWorkspace,
|
||||
type WorkspaceDescriptor,
|
||||
} from "../workspaces/schema.js";
|
||||
|
||||
export interface ThtConfig extends SecretBundleConfig {
|
||||
thtBin: string;
|
||||
@@ -25,6 +29,7 @@ export interface ThtConfig extends SecretBundleConfig {
|
||||
runtimeSnapshotRoot?: string;
|
||||
secretRoots?: readonly string[];
|
||||
semanticRuntime: SemanticRuntimeConfig;
|
||||
qdrantRequest?: typeof fetch;
|
||||
}
|
||||
|
||||
export interface RuntimeConfigLease {
|
||||
@@ -64,6 +69,24 @@ export interface OllamaEnsureResult {
|
||||
model_name?: string;
|
||||
}
|
||||
|
||||
export type SemanticReadinessCode = "workspace_not_activatable" | "semantic_index_incompatible";
|
||||
|
||||
export interface QdrantEnsureResult {
|
||||
ok: boolean;
|
||||
code?: SemanticReadinessCode;
|
||||
}
|
||||
|
||||
const REQUIRED_QDRANT_PAYLOAD_INDEXES = [
|
||||
"content_hash",
|
||||
"document_id",
|
||||
"kind",
|
||||
"record_key",
|
||||
"record_kind",
|
||||
"vector_generation",
|
||||
"workspace_id",
|
||||
"workspace_revision",
|
||||
] as const;
|
||||
|
||||
interface RuntimeSnapshot {
|
||||
path: string;
|
||||
dev: number;
|
||||
@@ -136,7 +159,7 @@ export class ThtRunner {
|
||||
if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size) {
|
||||
throw new Error("workspace snapshot changed while reading");
|
||||
}
|
||||
const workspace = parseWorkspaceYaml(source);
|
||||
const workspace = validateOperationalWorkspace(parseWorkspaceYaml(source));
|
||||
if (workspace.workspace.id !== identity.workspaceId) {
|
||||
throw new Error("workspace snapshot identity does not match its path");
|
||||
}
|
||||
@@ -537,4 +560,52 @@ export class ThtRunner {
|
||||
error: parsed?.error ?? (stderr.trim() || `tht ollama ensure exit ${code}`),
|
||||
};
|
||||
}
|
||||
|
||||
async qdrantEnsure(
|
||||
workspace: WorkspaceDescriptor,
|
||||
timeoutSec: number,
|
||||
): Promise<QdrantEnsureResult> {
|
||||
let descriptor;
|
||||
try {
|
||||
descriptor = validateOperationalWorkspace(workspace);
|
||||
} catch {
|
||||
return { ok: false, code: "workspace_not_activatable" };
|
||||
}
|
||||
const collection = descriptor.semantic_index.vector_store;
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), Math.max(1, timeoutSec) * 1000);
|
||||
try {
|
||||
const url = new URL(
|
||||
`/collections/${encodeURIComponent(collection.collection)}`,
|
||||
this.cfg.semanticRuntime.internalQdrantUrl,
|
||||
);
|
||||
const request = this.cfg.qdrantRequest ?? fetch;
|
||||
const response = await request(url.toString(), { method: "GET", signal: controller.signal });
|
||||
if (response.status === 404) {
|
||||
return { ok: false, code: "semantic_index_incompatible" };
|
||||
}
|
||||
if (!response.ok) return { ok: false, code: "workspace_not_activatable" };
|
||||
const body = await response.json() as any;
|
||||
const result = body?.result;
|
||||
const vectors = result?.config?.params?.vectors;
|
||||
const payloadSchema = result?.payload_schema;
|
||||
const configurationMatches = vectors
|
||||
&& vectors.size === collection.dimensions
|
||||
&& typeof vectors.distance === "string"
|
||||
&& vectors.distance.toLowerCase() === collection.distance;
|
||||
const indexesMatch = payloadSchema
|
||||
&& typeof payloadSchema === "object"
|
||||
&& REQUIRED_QDRANT_PAYLOAD_INDEXES.every(
|
||||
(field) => payloadSchema[field]?.data_type === "keyword",
|
||||
);
|
||||
return configurationMatches && indexesMatch
|
||||
? { ok: true }
|
||||
: { ok: false, code: "semantic_index_incompatible" };
|
||||
} catch {
|
||||
return { ok: false, code: "workspace_not_activatable" };
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
controller.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user