import { spawn } from "node:child_process"; import { closeSync, constants as fsConstants, openSync } from "node:fs"; import { join } from "node:path"; import { loadConfig, type AppConfig } from "./config.js"; import { ThtRunner } from "./tht/tht-runner.js"; import { WorkspaceRegistry } from "./workspaces/registry.js"; import { publishDeterministicRuntimeConfigLease, renderActiveWorkspaceRuntime } from "./workspaces/runtime-config-lease.js"; import { WorkspaceSecretStore } from "./workspaces/secret-store.js"; import { WorkspacePreprocessingService, type WorkspaceOperationResult } from "./workspaces/preprocessing-service.js"; import { createCatalogRepository } from "./catalog/repository.js"; import type { CatalogRepository } from "./catalog/types.js"; export interface WorkspaceMaintenanceIo { stdin: string; stdout: string[]; stderr: string[]; writeStdout(value: string): void; writeStderr(value: string): void; } type Command = "inspect" | "preprocess-run" | "preprocess-clear"; function failureResult( operation: string, workspaceId = "", code: WorkspaceOperationResult["code"] = "workspace_not_activatable", ): WorkspaceOperationResult { return { schemaVersion: 1, status: "failed", code, workspaceId, workspaceRevision: "", descriptorBlob: "", operation, completedStages: [], }; } const STATE_ERROR_CODES: Record = { preprocessing_resume_mismatch: "preprocessing_resume_mismatch", preprocessing_conflict: "preprocessing_conflict", effective_config_mismatch: "effective_config_mismatch", }; function boundedJson(result: WorkspaceOperationResult): string { const encoded = JSON.stringify(result); if (Buffer.byteLength(encoded, "utf8") > 1024 * 1024) { return JSON.stringify(failureResult(result.operation || "unknown", result.workspaceId)); } return encoded; } function sanitizeStderr(_error: unknown): string { // Never return raw exception text: it may embed endpoints, tokens, or SQL. return "workspace maintenance failed\n"; } function parseRequest(command: string, stdin: string): Record { const parsed = JSON.parse(stdin) as Record; if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.schemaVersion !== 1) { throw new Error("invalid request"); } const allowedByCommand: Record = { inspect: ["schemaVersion", "workspaceId"], "preprocess-run": ["schemaVersion", "workspaceId"], "preprocess-clear": ["schemaVersion", "workspaceId"], }; const allowed = allowedByCommand[command]; if (!allowed) throw new Error("unknown command"); if (typeof parsed.workspaceId !== "string") throw new Error("invalid workspace id"); for (const key of Object.keys(parsed)) if (!allowed.includes(key)) throw new Error("unexpected request field"); return parsed; } function exitCodeFor(result: WorkspaceOperationResult): number { if (["succeeded", "unchanged", "dry_run"].includes(result.status)) return 0; if (result.status === "blocked") return 3; return 1; } async function dispatch(command: Command, service: WorkspacePreprocessingService, request: Record): Promise { switch (command) { case "inspect": return await service.inspect({ workspaceId: request.workspaceId as string }); case "preprocess-run": return await service.run({ workspaceId: request.workspaceId as string, }); case "preprocess-clear": return await service.clear({ workspaceId: request.workspaceId as string }); } } export async function runWorkspaceMaintenanceCli( argv: readonly string[], service: WorkspacePreprocessingService, io: WorkspaceMaintenanceIo, ): Promise { const command = argv[2]; if (!command) { const result = failureResult("unknown"); io.writeStdout(boundedJson(result)); return 2; } try { const request = parseRequest(command, io.stdin); const result = await dispatch(command as Command, service, request); io.writeStdout(boundedJson(result)); return exitCodeFor(result); } catch (error) { const failureCode = error instanceof Error && "code" in error && typeof (error as { code?: unknown }).code === "string" && (error as { code: string }).code in STATE_ERROR_CODES ? STATE_ERROR_CODES[(error as { code: string }).code] : "workspace_not_activatable"; const result = failureResult(command, (() => { try { return JSON.parse(io.stdin).workspaceId ?? ""; } catch { return ""; } })(), failureCode); io.writeStdout(boundedJson(result)); io.writeStderr(sanitizeStderr(error)); const message = String((error as Error).message ?? ""); const requestError = error instanceof SyntaxError || message === "invalid request" || message === "unknown command" || message === "unexpected request field" || message === "invalid workspace id"; return command in { inspect: true, "preprocess-run": true, "preprocess-clear": true, } ? (requestError ? 2 : 1) : 2; } } export interface ProductionWorkspacePreprocessingDeps { config?: AppConfig; catalogRepository?: CatalogRepository; registry?: WorkspaceRegistry; workspaceSecretStore?: WorkspaceSecretStore; runner?: ThtRunner; } export function createProductionWorkspacePreprocessingService( deps: ProductionWorkspacePreprocessingDeps = {}, ): WorkspacePreprocessingService { const config = deps.config ?? loadConfig(process.env, { surface: "workspace-maintenance" }); const catalogRepository = deps.catalogRepository ?? createCatalogRepository(config.catalogDatabase); const registry = deps.registry ?? new WorkspaceRegistry(config.workspaceRegistry); const workspaceSecretStore = deps.workspaceSecretStore ?? new WorkspaceSecretStore({ root: config.workspaceSecretStoreRoot, runtimeRoot: config.workspaceSecretRuntimeRoot, installationId: config.workspaceRegistry.installationId, }); const runner = deps.runner ?? new ThtRunner({ thtBin: config.thtBin, harnessDir: config.harnessDir, configPath: process.env.THT_CONFIG ?? "config/tht.yaml", dataRoot: config.dataRoot, runtimeSnapshotRoot: join(config.workspaceRegistry.root, "snapshots", "runtime"), secretRoots: config.workspaceRegistry.secretRoots, secretsFile: config.secretsFile, secretFiles: config.secretFiles, workspaceSecretStore, semanticRuntime: { internalQdrantUrl: config.internalQdrantUrl, internalEmbeddingUrl: config.internalEmbeddingUrl, internalEmbeddingId: config.internalEmbeddingId, internalEmbeddingModel: config.internalEmbeddingModel, internalEmbeddingDimensions: config.internalEmbeddingDimensions, }, }); return new WorkspacePreprocessingService({ dataRoot: config.dataRoot ?? "/data", httpPrivateHostAllowlist: (process.env.THT_EVIDENCE_PRIVATE_HOST_ALLOWLIST ?? "") .split(",").map((value) => value.trim()).filter((value) => value.length > 0), catalogRepository, acquireActiveRuntime: async (workspaceId) => { const catalogDatabase = await catalogRepository.getByWorkspace(workspaceId); if (!catalogDatabase) throw new Error("workspace database is not configured in the Catalog"); const active = await renderActiveWorkspaceRuntime({ workspaceId, registry, registryConfig: config.workspaceRegistry, harnessDir: config.harnessDir, configPath: process.env.THT_CONFIG ?? "config/tht.yaml", dataRoot: config.dataRoot ?? "/data", secretRoots: config.workspaceRegistry.secretRoots, workspaceSecretStore, catalogDatabase, semanticRuntime: { internalQdrantUrl: config.internalQdrantUrl, internalEmbeddingUrl: config.internalEmbeddingUrl, internalEmbeddingModel: config.internalEmbeddingModel, internalEmbeddingDimensions: config.internalEmbeddingDimensions, }, }); try { const configLease = await publishDeterministicRuntimeConfigLease({ workspaceId, registry, registryConfig: config.workspaceRegistry, harnessDir: config.harnessDir, configPath: process.env.THT_CONFIG ?? "config/tht.yaml", dataRoot: config.dataRoot ?? "/data", secretRoots: config.workspaceRegistry.secretRoots, semanticRuntime: { internalQdrantUrl: config.internalQdrantUrl, internalEmbeddingUrl: config.internalEmbeddingUrl, internalEmbeddingModel: config.internalEmbeddingModel, internalEmbeddingDimensions: config.internalEmbeddingDimensions, }, workspaceSecretStore, catalogDatabase, }); return { workspace: active.workspace, workspaceId: active.workspaceId, workspaceRevision: active.workspaceRevision, descriptorBlob: active.descriptorBlob, catalogBlob: active.catalogBlob, configLease, }; } finally { active.releaseSecrets(); } }, runChild: async ({ argv, configPath }) => { const configFd = openSync(configPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW); try { return await new Promise((resolve) => { const childEnvironment = { ...process.env }; for (const name of [ "THT_CATALOG_DATABASE_URL", "THT_CATALOG_DB_HOST", "THT_CATALOG_DB_PORT", "THT_CATALOG_DB_NAME", "THT_CATALOG_RUNTIME_USER", "THT_CATALOG_RUNTIME_PASSWORD_FILE", "THT_CATALOG_MIGRATOR_DATABASE_URL", "THT_CATALOG_MIGRATOR_USER", "THT_CATALOG_MIGRATOR_PASSWORD_FILE", ]) delete childEnvironment[name]; const child = spawn(config.thtBin, argv, { cwd: config.harnessDir, env: { ...childEnvironment, ...(config.dataRoot ? { THT_DATA_ROOT: config.dataRoot } : {}) }, stdio: ["ignore", "pipe", "pipe", configFd], }); let stdout = ""; let stderr = ""; child.stdout?.on("data", (chunk: Buffer) => { stdout += chunk.toString("utf8"); }); child.stderr?.on("data", (chunk: Buffer) => { stderr += chunk.toString("utf8"); }); child.on("close", (code) => resolve({ exitCode: code ?? 0, stdout, stderr: stderr.slice(0, 64 * 1024) })); child.on("error", (error) => resolve({ exitCode: 1, stdout, stderr: String(error.message).slice(0, 4096) })); }); } finally { closeSync(configFd); } }, semanticPreflight: async (workspace) => { const result = await runner.qdrantEnsure(workspace, 30, "self_heal"); return result.ok ? { ok: true as const } : { ok: false as const, code: result.code ?? "workspace_not_activatable" }; }, evidencePreflight: async (workspace) => { const result = await runner.qdrantEnsure(workspace, 30, "evidence_maintenance"); return result.ok ? { ok: true as const } : { ok: false as const, code: result.code ?? "workspace_not_activatable" }; }, }); } if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) { const stdout: string[] = []; const stderr: string[] = []; const io: WorkspaceMaintenanceIo = { stdin: await new Promise((resolve) => { let input = ""; process.stdin.setEncoding("utf8"); process.stdin.on("data", (chunk) => { input += chunk; }); process.stdin.on("end", () => resolve(input)); }), stdout, stderr, writeStdout: (value) => { stdout.push(value); }, writeStderr: (value) => { stderr.push(value); }, }; const exitCode = await runWorkspaceMaintenanceCli( process.argv, createProductionWorkspacePreprocessingService(), io, ); process.stdout.write(stdout.join("")); if (stderr.length > 0) process.stderr.write(stderr.join("").slice(0, 64 * 1024)); process.exit(exitCode); }