237 lines
11 KiB
TypeScript
237 lines
11 KiB
TypeScript
import { constants, accessSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
import { basename, dirname, join } from "node:path";
|
|
import { createRequire } from "node:module";
|
|
import { resolveRuntimeBindings } from "../backend/src/workspaces/bindings.js";
|
|
import { renderRuntimeConfig } from "../backend/src/workspaces/runtime-renderer.js";
|
|
import { validateWorkspaceDescriptor } from "../backend/src/workspaces/schema.js";
|
|
|
|
const requireFromBackend = createRequire(new URL("../backend/package.json", import.meta.url));
|
|
const { parse } = requireFromBackend("yaml") as { parse: (value: string) => any };
|
|
|
|
const [renderedPath, workspacePath, profile, bundleSource, runtimePasswordSourceInput] = process.argv.slice(2);
|
|
if (!renderedPath || !workspacePath || (profile !== "local" && profile !== "server")
|
|
|| !bundleSource || !runtimePasswordSourceInput) {
|
|
throw new Error(
|
|
"usage: task13-runtime-fixture-check RENDERED_JSON WORKSPACE_YAML local|server BUNDLE PASSWORD",
|
|
);
|
|
}
|
|
|
|
const config = JSON.parse(readFileSync(renderedPath, "utf8"));
|
|
const workspace = parse(readFileSync(workspacePath, "utf8"));
|
|
const core = config.services?.core;
|
|
const frontend = config.services?.frontend;
|
|
if (!core || !frontend) throw new Error("fixture render must contain core and frontend");
|
|
const qdrant = config.services?.qdrant;
|
|
const embedding = config.services?.embedding;
|
|
const modelInit = config.services?.["embedding-model-init"];
|
|
if (!qdrant || !embedding || !modelInit) {
|
|
throw new Error("fixture render must contain the private semantic services");
|
|
}
|
|
|
|
for (const [name, service, expectedExpose] of [
|
|
["qdrant", qdrant, "6333"],
|
|
["embedding", embedding, "11434"],
|
|
] as const) {
|
|
const localQdrantDashboard = name === "qdrant" && profile === "local"
|
|
&& (service.ports || []).length === 1
|
|
&& service.ports[0].host_ip === "127.0.0.1" && Number(service.ports[0].target) === 6333;
|
|
if ((service.ports || []).length !== 0 && !localQdrantDashboard) {
|
|
throw new Error(`${name} must not publish host ports outside the local Qdrant dashboard`);
|
|
}
|
|
if ((service.expose || []).join(",") !== expectedExpose) {
|
|
throw new Error(`${name} must expose only ${expectedExpose}`);
|
|
}
|
|
}
|
|
if ((modelInit.ports || []).length !== 0) {
|
|
throw new Error("embedding-model-init must not publish host ports");
|
|
}
|
|
|
|
const expected = {
|
|
THT_WS_TASK13_SMOKE_DWH_TRANSPORT: "postgres_direct",
|
|
THT_WS_TASK13_SMOKE_DWH_HOST: "dwh.task13.invalid",
|
|
THT_WS_TASK13_SMOKE_DWH_PORT: "5432",
|
|
THT_WS_TASK13_SMOKE_DWH_USER: "task13_reader",
|
|
THT_WS_TASK13_SMOKE_DWH_PASSWORD_FILE: "/run/secrets/task13-runtime-password",
|
|
};
|
|
for (const [name, value] of Object.entries(expected)) {
|
|
if (core.environment?.[name] !== value) {
|
|
throw new Error(`core runtime binding ${name} is ${JSON.stringify(core.environment?.[name])}, want ${JSON.stringify(value)}`);
|
|
}
|
|
if (Object.hasOwn(frontend.environment || {}, name)) {
|
|
throw new Error(`runtime binding escaped to frontend: ${name}`);
|
|
}
|
|
}
|
|
|
|
const semanticRuntime = {
|
|
THT_INTERNAL_QDRANT_URL: "http://qdrant:6333",
|
|
THT_INTERNAL_EMBEDDING_URL: "http://embedding:11434",
|
|
THT_INTERNAL_EMBEDDING_MODEL: "qwen3-embedding:0.6b",
|
|
THT_INTERNAL_EMBEDDING_DIMENSIONS: "1024",
|
|
};
|
|
for (const [name, value] of Object.entries(semanticRuntime)) {
|
|
if (core.environment?.[name] !== value) {
|
|
throw new Error(`core semantic runtime ${name} is ${JSON.stringify(core.environment?.[name])}, want ${JSON.stringify(value)}`);
|
|
}
|
|
if (Object.hasOwn(frontend.environment || {}, name)) {
|
|
throw new Error(`semantic runtime escaped to frontend: ${name}`);
|
|
}
|
|
}
|
|
for (const name of [
|
|
["THT", "VEC", "REST", "URL"].join("_"),
|
|
["THT", "VEC", "WRITE", "REST", "URL"].join("_"),
|
|
["THT", "OLLAMA", "URL"].join("_"),
|
|
]) {
|
|
if (Object.hasOwn(core.environment || {}, name) && core.environment?.[name] !== "") {
|
|
throw new Error(`fixture render reintroduced external semantic binding ${name}`);
|
|
}
|
|
}
|
|
|
|
if (workspace.workspace?.id !== "task13-smoke") throw new Error("fixture workspace id changed");
|
|
if (workspace.workspace?.schema_version !== 4) throw new Error("fixture workspace must use schema v4");
|
|
if (Object.hasOwn(workspace, "dwh")) {
|
|
throw new Error("authored workspace fixture must not contain database metadata");
|
|
}
|
|
for (const forbidden of ["semantic_index", "llm_policy"]) {
|
|
if (Object.hasOwn(workspace, forbidden)) {
|
|
throw new Error(`fixture workspace must not own installation model configuration: ${forbidden}`);
|
|
}
|
|
}
|
|
|
|
if (!statSync(bundleSource).isFile()) {
|
|
throw new Error("fixture secret bundle source is not a regular file");
|
|
}
|
|
accessSync(bundleSource, constants.R_OK);
|
|
const coreBundle = (core.secrets || []).filter(
|
|
(secret: any) => secret.source === "thothii_secrets" && secret.target === "thothii.secrets",
|
|
);
|
|
if (profile === "local") {
|
|
if (coreBundle.length !== 0) throw new Error("local core retained the host-owned secret bundle mount");
|
|
} else if (coreBundle.length !== 1) {
|
|
throw new Error("server core lacks exactly one runtime secret bundle mount");
|
|
}
|
|
if ((frontend.secrets || []).length !== 0) throw new Error("frontend received a runtime secret");
|
|
|
|
const mounts = core.volumes || [];
|
|
const runtimePasswordMounts = mounts.filter(
|
|
(mount: any) => mount.target === "/run/secrets/task13-runtime-password",
|
|
);
|
|
if (profile === "local") {
|
|
const projected = mounts.filter((mount: any) => mount.target === "/run/secrets");
|
|
if (runtimePasswordMounts.length !== 0 || projected.length !== 1
|
|
|| projected[0].type !== "volume" || !projected[0].read_only
|
|
|| (projected[0].source !== "application-secrets"
|
|
&& !projected[0].source.endsWith("_application-secrets"))) {
|
|
throw new Error("local secrets are not isolated in the Compose-owned projection volume");
|
|
}
|
|
} else if (runtimePasswordMounts.length !== 1 || runtimePasswordMounts[0].type !== "bind"
|
|
|| !runtimePasswordMounts[0].read_only || !statSync(runtimePasswordMounts[0].source).isFile()) {
|
|
throw new Error("server runtime fixture lacks one readable, read-only password-file bind");
|
|
}
|
|
accessSync(runtimePasswordSourceInput, constants.R_OK);
|
|
const generatedPiTargets = [
|
|
"/home/thoth/.pi/agent/models.json",
|
|
"/home/thoth/.pi/agent/settings.json",
|
|
] as const;
|
|
const piParent = mounts.filter((mount: any) => mount.target === "/home/thoth/.pi");
|
|
if (piParent.length !== 1) throw new Error("core lacks exactly one Pi state mount");
|
|
for (const target of generatedPiTargets) {
|
|
const selected = mounts.filter((mount: any) => mount.target === target);
|
|
if (selected.length !== 1 || selected[0].type !== "bind" || !selected[0].read_only) {
|
|
throw new Error(`Pi fixture mount is not one generated read-only bind: ${target}`);
|
|
}
|
|
accessSync(selected[0].source, constants.R_OK);
|
|
if (profile === "local") {
|
|
if (piParent[0].type !== "volume") {
|
|
throw new Error(`local Pi parent is not a state volume: ${target}`);
|
|
}
|
|
} else {
|
|
const hidden = join(piParent[0].source, "agent", basename(target));
|
|
if (!statSync(hidden).isFile()) throw new Error(`server parent root lacks ${hidden}`);
|
|
}
|
|
}
|
|
const authTarget = "/home/thoth/.pi/agent/auth.json";
|
|
const authMounts = mounts.filter((mount: any) => mount.target === authTarget);
|
|
if (profile === "local") {
|
|
if (authMounts.length !== 0) {
|
|
throw new Error("local Pi auth must be projected into the state volume, not directly mounted");
|
|
}
|
|
} else if (authMounts.length !== 1 || authMounts[0].type !== "bind" || !authMounts[0].read_only) {
|
|
throw new Error("server Pi auth is not one independent read-only bind");
|
|
}
|
|
|
|
for (const [target, localVolume] of [
|
|
["/run/thothii-auth", "auth-runtime"],
|
|
["/fixtures/remote.git", "registry-remote"],
|
|
] as const) {
|
|
const selected = mounts.filter((mount: any) => mount.target === target);
|
|
if (selected.length !== 1 || !selected[0].read_only) {
|
|
throw new Error(`core lacks exactly one read-only runtime mount: ${target}`);
|
|
}
|
|
if (profile === "local") {
|
|
if (selected[0].type !== "volume"
|
|
|| (selected[0].source !== localVolume && !selected[0].source.endsWith(`_${localVolume}`))) {
|
|
throw new Error(`local runtime fixture is not projected through ${localVolume}`);
|
|
}
|
|
} else if (selected[0].type !== "bind") {
|
|
throw new Error(`server runtime fixture is not one read-only bind: ${target}`);
|
|
}
|
|
}
|
|
|
|
const resolverEnvironment = { ...core.environment };
|
|
const runtimePasswordSource = realpathSync(runtimePasswordSourceInput);
|
|
resolverEnvironment.THT_WS_TASK13_SMOKE_DWH_PASSWORD_FILE = runtimePasswordSource;
|
|
// Production projects the current PostgreSQL Catalog row into an ephemeral runtime descriptor.
|
|
// Keep the authored fixture database-free and reproduce that projection only for this contract check.
|
|
const runtimeWorkspace = validateWorkspaceDescriptor({
|
|
...workspace,
|
|
dwh: {
|
|
engine: "postgres",
|
|
database: "warehouse",
|
|
schema: "datawarehouse",
|
|
port: 5432,
|
|
supported_transports: ["postgres_direct"],
|
|
},
|
|
});
|
|
const bindings = resolveRuntimeBindings(
|
|
runtimeWorkspace,
|
|
resolverEnvironment,
|
|
[dirname(runtimePasswordSource)],
|
|
);
|
|
for (const [role, binding] of Object.entries(bindings)) {
|
|
if ((binding as any).missing.length !== 0) {
|
|
throw new Error(`workspace resolver reports missing ${role} bindings: ${(binding as any).missing.join(",")}`);
|
|
}
|
|
}
|
|
const runtime = parse(renderRuntimeConfig(runtimeWorkspace, bindings, {
|
|
sessions: "/data/sessions",
|
|
artifacts: "/data/artifacts",
|
|
indexes: "/data/indexes",
|
|
}, {
|
|
workspaceId: "task13-smoke",
|
|
workspaceRevision: "task13-fixture",
|
|
revisionContentRoot: join(dirname(workspacePath), "task13-fixture"),
|
|
}));
|
|
if (runtime.database.host !== expected.THT_WS_TASK13_SMOKE_DWH_HOST
|
|
|| runtime.database.user !== expected.THT_WS_TASK13_SMOKE_DWH_USER
|
|
|| runtime.database.password_file !== runtimePasswordSource
|
|
|| runtime.database.database !== "warehouse"
|
|
|| runtime.database.schema !== "datawarehouse") {
|
|
throw new Error("workspace resolver produced the wrong DWH runtime");
|
|
}
|
|
if (runtime.resources?.vector?.base_url !== "http://qdrant:6333"
|
|
|| runtime.resources?.vector?.collections?.reference !== "task13-smoke-reference"
|
|
|| runtime.resources?.vector?.collections?.memory !== "task13-smoke-memory") {
|
|
throw new Error("workspace resolver produced the wrong qdrant runtime");
|
|
}
|
|
if (runtime.resources?.embeddings?.base_url !== "http://embedding:11434"
|
|
|| runtime.resources?.embeddings?.model !== "qwen3-embedding:0.6b"
|
|
|| runtime.resources?.embeddings?.dimensions !== 1024) {
|
|
throw new Error("workspace resolver produced the wrong embedding runtime");
|
|
}
|
|
const secret = readFileSync(bundleSource, "utf8").trim();
|
|
const runtimePassword = readFileSync(runtimePasswordSourceInput, "utf8");
|
|
if (JSON.stringify(config).includes(secret)) throw new Error("fixture render leaked application bundle content");
|
|
if (JSON.stringify(runtime).includes(secret)) throw new Error("runtime render leaked application bundle content");
|
|
if (JSON.stringify(config).includes(runtimePassword)) throw new Error("fixture render leaked runtime password content");
|
|
if (JSON.stringify(runtime).includes(runtimePassword)) throw new Error("runtime render leaked runtime password content");
|