Files
ThothII/frontend/e2e/fixtures/auth-stack.mjs
T
Codex 82e2c91f42
Publish documentation / publish (push) Successful in 1m27s
feat: implement memory and evidence administration with guided repairs
Add PostgreSQL-backed memory, editable evidence with source review and activation, and human-approved archive repairs across the harness, API, and UI. Include migrations, deployment support, regression coverage, and validation documentation.

Refresh permissions from validated session roles so existing administrator logins can access newly deployed archive management features.
2026-09-10 10:31:34 +02:00

627 lines
24 KiB
JavaScript

import { spawn } from "node:child_process";
import { argon2 } from "node:crypto";
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
import { createServer as createHttpServer } from "node:http";
import { request as httpsRequest } from "node:https";
import { createServer } from "node:net";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { startFakeOidcProvider } from "../../../backend/test/fixtures/oidc-provider.mjs";
const __dir = dirname(fileURLToPath(import.meta.url));
const repositoryRoot = resolve(__dir, "../../..");
const frontendRoot = join(repositoryRoot, "frontend");
const backendRoot = join(repositoryRoot, "backend");
const harnessRoot = join(repositoryRoot, "harness");
const thtRoot = join(repositoryRoot, "tools", "tht");
const fakePi = join(frontendRoot, "e2e", "fixtures", "fake-pi.mjs");
const fakeTht = join(frontendRoot, "e2e", "fixtures", "fake-tht.mjs");
const FIXTURE_CLIENT_ID = "thothii-e2e-client";
const DEFAULT_FIXTURE_CLIENT_SECRET = "e2e-client-secret-not-a-production-secret";
const DEFAULT_FIXTURE_API_TOKEN = "e2e-group-catalog-token-not-a-production-secret";
const WRONG_CLIENT_ID = "thothii-e2e-unregistered-client";
const WRONG_CLIENT_SECRET = "e2e-wrong-client-secret-not-production";
const WRONG_API_TOKEN = "e2e-wrong-api-token-not-production";
const OIDC_CREDENTIAL_VARIANTS = new Set([
"correct", "wrong-client-id", "wrong-client-secret", "wrong-api-token",
]);
function safeError(code) {
return new Error(code);
}
function resolveFixtureCredentials() {
const runtimeCredential = process.env.THT_TASK15_SENTINEL;
if (runtimeCredential === undefined) {
return Object.freeze({
clientSecret: DEFAULT_FIXTURE_CLIENT_SECRET,
apiToken: DEFAULT_FIXTURE_API_TOKEN,
runtime: false,
});
}
if (runtimeCredential.length < 24 || runtimeCredential.length > 512 || /[\r\n\0]/u.test(runtimeCredential)) {
throw safeError("e2e_runtime_fixture_credential_invalid");
}
return Object.freeze({ clientSecret: runtimeCredential, apiToken: runtimeCredential, runtime: true });
}
function buildAuthenticationStorageBridge(output) {
const result = spawn("go", ["build", "-o", output, "./cmd/tht"], {
cwd: thtRoot,
stdio: "ignore",
});
return new Promise((resolveBuild, rejectBuild) => {
result.once("error", () => rejectBuild(safeError("e2e_auth_storage_build_failed")));
result.once("exit", (code) => code === 0 ? resolveBuild() : rejectBuild(safeError("e2e_auth_storage_build_failed")));
});
}
function secureDirectory(path) {
mkdirSync(path, { recursive: true, mode: 0o700 });
chmodSync(path, 0o700);
}
function runFixtureCommand(command, args, cwd) {
const child = spawn(command, args, { cwd, stdio: "ignore" });
return new Promise((resolveCommand, rejectCommand) => {
child.once("error", () => rejectCommand(safeError("e2e_workspace_fixture_command_failed")));
child.once("exit", (code) => code === 0
? resolveCommand()
: rejectCommand(safeError("e2e_workspace_fixture_command_failed")));
});
}
const F1_WORKSPACE_ID = "fixture-workspace";
const F1_WORKSPACE_DESCRIPTOR = `workspace:
schema_version: 4
id: fixture-workspace
name: Fixture workspace
language: en
dwh:
engine: postgres
database: fixture
schema: fixture
supported_transports: [postgres_direct]
`;
async function prepareF1Workspace(root, memoryOnly = false) {
const source = join(root, "workspace-source");
const remote = join(root, "workspace-remote.git");
secureDirectory(source);
secureDirectory(join(source, F1_WORKSPACE_ID));
writeSecure(join(source, "thoth-workspaces.yaml"), [
"schema_version: 1",
"workspaces:",
` - id: ${F1_WORKSPACE_ID}`,
" name: Fixture workspace",
"",
].join("\n"));
writeSecure(join(source, F1_WORKSPACE_ID, "workspace.yaml"),
memoryOnly ? F1_WORKSPACE_DESCRIPTOR.split("dwh:")[0] : F1_WORKSPACE_DESCRIPTOR);
await runFixtureCommand("git", ["init", "--bare", "--initial-branch=main", remote], root);
chmodSync(remote, 0o700);
await runFixtureCommand("git", ["init", "--initial-branch=main"], source);
await runFixtureCommand("git", ["config", "user.name", "ThothII E2E Fixture"], source);
await runFixtureCommand("git", ["config", "user.email", "thothii-e2e@example.invalid"], source);
await runFixtureCommand("git", ["add", "-A"], source);
await runFixtureCommand("git", ["commit", "-m", "Create deterministic fixture workspace"], source);
await runFixtureCommand("git", ["remote", "add", "origin", remote], source);
await runFixtureCommand("git", ["push", "origin", "main"], source);
return { id: F1_WORKSPACE_ID, remote };
}
function writeSecure(path, value) {
writeFileSync(path, value, { encoding: "utf8", mode: 0o600 });
chmodSync(path, 0o600);
}
async function testPasswordHash(password) {
const salt = Buffer.from("thothii-e2e-salt");
const message = Buffer.from(password, "utf8");
let digest;
try {
digest = await new Promise((resolveDigest, rejectDigest) => {
argon2("argon2id", {
message,
nonce: salt,
memory: 65_536,
passes: 3,
parallelism: 1,
tagLength: 32,
}, (error, derived) => error || !derived ? rejectDigest(error ?? safeError("e2e_password_hash_failed")) : resolveDigest(derived));
});
return `$argon2id$v=19$m=65536,t=3,p=1$${salt.toString("base64").replaceAll("=", "")}$${digest.toString("base64").replaceAll("=", "")}`;
} finally {
message.fill(0);
salt.fill(0);
digest?.fill(0);
}
}
function pause(milliseconds) {
return new Promise((resolvePause) => setTimeout(resolvePause, milliseconds));
}
function providerJson(url, caFile, options = {}) {
return new Promise((resolveResponse, rejectResponse) => {
const body = options.body ?? "";
const request = httpsRequest(url, {
method: options.method ?? "GET",
ca: readFileSync(caFile),
headers: {
accept: "application/json",
...(body.length === 0 ? {} : {
"content-length": String(Buffer.byteLength(body)),
"content-type": "application/x-www-form-urlencoded",
}),
...options.headers,
},
}, (response) => {
const chunks = [];
let size = 0;
response.on("data", (chunk) => {
size += chunk.length;
if (size > 64 * 1024) request.destroy(safeError("e2e_provider_response_too_large"));
else chunks.push(chunk);
});
response.once("error", () => rejectResponse(safeError("e2e_provider_response_failed")));
response.once("end", () => {
try {
resolveResponse({ status: response.statusCode ?? 0, body: JSON.parse(Buffer.concat(chunks).toString("utf8")) });
} catch {
rejectResponse(safeError("e2e_provider_response_invalid"));
}
});
});
request.once("error", () => rejectResponse(safeError("e2e_provider_request_failed")));
request.end(body);
});
}
async function freeLoopbackPort() {
const server = createServer();
await new Promise((resolveListen, rejectListen) => {
server.once("error", rejectListen);
server.listen({ host: "127.0.0.1", port: 0 }, resolveListen);
});
const address = server.address();
await new Promise((resolveClose) => server.close(resolveClose));
if (!address || typeof address === "string") throw safeError("e2e_loopback_port_unavailable");
return address.port;
}
const F1_QDRANT_INDEXES = Object.freeze([
"content_hash", "document_id", "kind", "record_key",
"record_kind", "vector_generation", "workspace_id", "workspace_revision",
]);
async function startFakeQdrant() {
const payloadSchema = Object.fromEntries(F1_QDRANT_INDEXES.map((field) => [field, { data_type: "keyword" }]));
const server = createHttpServer((request, response) => {
const path = new URL(request.url ?? "/", "http://loopback.invalid").pathname;
if (request.method !== "GET" || path !== `/collections/${F1_WORKSPACE_ID}`) {
response.writeHead(404).end();
return;
}
response.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({
result: {
config: { params: { vectors: { size: 1024, distance: "Cosine" } } },
payload_schema: payloadSchema,
},
}));
});
await new Promise((resolveListen, rejectListen) => {
server.once("error", rejectListen);
server.listen({ host: "127.0.0.1", port: 0 }, resolveListen);
});
const address = server.address();
if (!address || typeof address === "string") {
await new Promise((resolveClose) => server.close(resolveClose));
throw safeError("e2e_qdrant_loopback_port_unavailable");
}
return {
baseUrl: `http://127.0.0.1:${address.port}/`,
close: () => new Promise((resolveClose) => server.close(resolveClose)),
};
}
function managedProcess(command, args, options) {
const child = spawn(command, args, {
cwd: options.cwd,
env: options.env,
stdio: ["ignore", "pipe", "pipe"],
});
let exited = false;
let diagnostic = "";
const captureDiagnostic = (chunk) => {
const sanitized = String(chunk)
.replace(/https?:\/\/[^\s)]+/g, "[url]")
.replace(/(?:THT_[A-Z_]+|PI_[A-Z_]+|AUTH_MODE)=\S+/g, "$1=[redacted]")
.replace(/[A-Za-z0-9_-]{43,}/g, "[redacted]");
diagnostic = `${diagnostic}${sanitized}`.slice(-800);
};
child.once("exit", () => { exited = true; });
child.stdout?.on("data", captureDiagnostic);
child.stderr?.on("data", captureDiagnostic);
return {
child,
exited: () => exited,
diagnostic: () => diagnostic.replace(/\s+/g, " ").trim(),
async close() {
if (exited) return;
child.kill("SIGTERM");
for (let attempt = 0; attempt < 50 && !exited; attempt += 1) await pause(20);
if (!exited) child.kill("SIGKILL");
for (let attempt = 0; attempt < 50 && !exited; attempt += 1) await pause(20);
},
};
}
function oneShotJson(command, args, options) {
return new Promise((resolveCommand) => {
const child = spawn(command, args, {
cwd: options.cwd,
env: options.env,
stdio: ["ignore", "pipe", "ignore"],
});
let output = "";
let outputValid = true;
let settled = false;
const finish = (status) => {
if (settled) return;
settled = true;
clearTimeout(timer);
let report;
try {
report = outputValid ? JSON.parse(output) : undefined;
} catch {
report = undefined;
}
resolveCommand({
status: Number.isInteger(status) ? status : 1,
report: report && typeof report === "object"
? report
: { ready: false, mode: "none", checks: [{ code: "fixture_command_invalid" }] },
});
};
child.stdout?.on("data", (chunk) => {
if (!outputValid) return;
output += String(chunk);
if (Buffer.byteLength(output) > 64 * 1024) {
output = "";
outputValid = false;
child.kill("SIGKILL");
}
});
child.once("error", () => finish(1));
child.once("exit", (status) => finish(status));
const timer = setTimeout(() => child.kill("SIGKILL"), 35_000);
});
}
async function waitForOk(url, processHandle) {
for (let attempt = 0; attempt < 300; attempt += 1) {
if (processHandle.exited()) {
const detail = processHandle.diagnostic();
throw safeError(detail ? `e2e_service_stopped_during_startup:${detail}` : "e2e_service_stopped_during_startup");
}
try {
const response = await fetch(url, { redirect: "error" });
if (response.ok) return;
} catch {
// The process is expected to race its listener setup.
}
await pause(100);
}
throw safeError("e2e_service_startup_timeout");
}
function cleanBackendEnvironment(overrides) {
const env = { ...process.env };
for (const name of [
"AUTH_MODE", "THT_AUTH_CONFIG_FILE", "THT_AUTH_STATE_ROOT", "THT_SECRETS_FILE", "NODE_EXTRA_CA_CERTS",
"SETTINGS_FILE", "THT_MAINTENANCE_FILE", "THT_WORKSPACE_REGISTRY_ROOT", "THT_WORKSPACE_SECRET_STORE_ROOT",
"THT_WORKSPACE_SECRET_RUNTIME_ROOT", "THT_WORKSPACE_GIT_REMOTE", "THT_WORKSPACE_GIT_BRANCH",
"THT_WORKSPACE_SECRET_ROOTS", "THT_WORKSPACE_INSTALLATION_ID", "THT_LEGACY_WORKSPACE_MODE",
"THT_DWH_PRECHECK", "THT_INTERNAL_QDRANT_URL", "THT_CONFIG", "THT_TASK15_SENTINEL",
]) delete env[name];
for (const name of Object.keys(env)) {
if (name.startsWith("THT_WS_")) delete env[name];
}
return { ...env, ...overrides };
}
export async function createAuthenticationStack({ withF1Workspace = false, memoryRuntime = {} } = {}) {
const credentials = resolveFixtureCredentials();
const root = mkdtempSync(join(realpathSync(tmpdir()), "thothii-auth-e2e-"));
secureDirectory(root);
const stateRoot = join(root, "auth-state");
const registryRoot = join(root, "workspace-registry");
const workspaceSecretRoot = join(root, "workspace-secrets");
const workspaceRuntimeRoot = join(root, "workspace-runtime");
const fixtureSecretRoot = join(root, "fixture-runtime-secrets");
const piAgentRoot = join(root, "pi-agent");
const providerRoot = join(root, "provider");
const authConfigFile = join(root, "auth.yaml");
const usersFile = join(root, "users.yaml");
const secretsFile = join(root, "test.secrets");
const settingsFile = join(root, "settings.json");
const maintenanceFile = join(root, "maintenance.json");
const authStorageBinary = join(root, "tht-auth-storage");
const fixtureDwhPasswordFile = join(fixtureSecretRoot, "fixture-dwh-password");
const fixtureDwhCaFile = join(fixtureSecretRoot, "fixture-dwh-ca.pem");
for (const path of [stateRoot, registryRoot, workspaceSecretRoot, workspaceRuntimeRoot, fixtureSecretRoot, piAgentRoot, providerRoot]) secureDirectory(path);
for (const child of ["sessions", "oidc"]) secureDirectory(join(stateRoot, child));
writeSecure(join(piAgentRoot, "settings.json"), JSON.stringify({
enabledModels: ["zai/glm-5.2"],
}));
writeSecure(join(piAgentRoot, "auth.json"), JSON.stringify({
zai: { type: "api_key", key: "e2e-model-key-not-a-production-secret" },
}));
const [frontendPort, backendPort] = await Promise.all([freeLoopbackPort(), freeLoopbackPort()]);
const publicUrl = `http://127.0.0.1:${frontendPort}`;
const backendUrl = `http://127.0.0.1:${backendPort}`;
const workspace = withF1Workspace ? await prepareF1Workspace(root, Object.keys(memoryRuntime).length > 0) : undefined;
const provider = await startFakeOidcProvider({
directory: providerRoot,
registration: {
clientId: FIXTURE_CLIENT_ID,
clientSecret: credentials.clientSecret,
redirectUri: new URL("/api/auth/oidc/callback", publicUrl).href,
},
apiToken: credentials.apiToken,
});
await buildAuthenticationStorageBridge(authStorageBinary);
const localPassword = "e2e-local-password";
const passwordHash = await testPasswordHash(localPassword);
const accounts = Object.freeze({
ordinary: Object.freeze({ username: "ordinary", password: localPassword }),
admin: Object.freeze({ username: "administrator", password: localPassword }),
});
writeSecure(usersFile, JSON.stringify({
version: 1,
users: [
{
id: "11111111-1111-4111-8111-111111111111",
username: accounts.ordinary.username,
displayName: "Fixture ordinary",
passwordHash,
roles: ["user"],
enabled: true,
authRevision: 1,
},
{
id: "22222222-2222-4222-8222-222222222222",
username: accounts.admin.username,
displayName: "Fixture administrator",
passwordHash,
roles: ["admin"],
enabled: true,
authRevision: 1,
},
],
}));
function writeOidcSecrets(variant) {
if (!OIDC_CREDENTIAL_VARIANTS.has(variant)) throw safeError("e2e_oidc_credential_variant_invalid");
const selectedClientSecret = variant === "wrong-client-secret" ? WRONG_CLIENT_SECRET : credentials.clientSecret;
const selectedApiToken = variant === "wrong-api-token" ? WRONG_API_TOKEN : credentials.apiToken;
writeSecure(secretsFile, [
`THT_OIDC_CLIENT_SECRET=${selectedClientSecret}`,
`THT_AUTHENTIK_API_TOKEN=${selectedApiToken}`,
"",
].join("\n"));
}
writeOidcSecrets("correct");
if (workspace) {
writeSecure(fixtureDwhPasswordFile, "fixture-password-not-a-secret\n");
writeSecure(fixtureDwhCaFile, "fixture-ca-not-a-production-certificate\n");
writeSecure(settingsFile, JSON.stringify({
workspace: workspace.id,
provider: "zai",
model: "glm-5.2",
thinking: "medium",
}));
}
let mode = undefined;
let backend = undefined;
let qdrant = undefined;
const frontend = managedProcess(join(frontendRoot, "node_modules", ".bin", "vite"), [
"--host", "127.0.0.1", "--port", String(frontendPort), "--strictPort",
], {
cwd: frontendRoot,
env: {
...process.env,
THT_FRONTEND_API_UPSTREAM: backendUrl,
},
});
try {
await waitForOk(publicUrl, frontend);
qdrant = workspace ? await startFakeQdrant() : undefined;
const localConfig = () => ({
version: 1,
mode: "local",
publicUrl,
session: {
regularTtlSeconds: 600,
regularIdleSeconds: 600,
rememberTtlSeconds: 2_592_000,
rememberIdleSeconds: 604_800,
},
local: { usersFile: "users.yaml" },
});
const oidcConfig = (variant = "correct") => ({
version: 1,
mode: "oidc",
publicUrl,
session: {
regularTtlSeconds: 600,
regularIdleSeconds: 600,
oidcTtlSeconds: 60,
},
oidc: {
issuer: provider.issuer,
clientId: variant === "wrong-client-id" ? WRONG_CLIENT_ID : FIXTURE_CLIENT_ID,
clientSecretRef: "THT_OIDC_CLIENT_SECRET",
scopes: ["openid", "profile", "groups"],
groupsClaim: "groups",
},
groupCatalog: {
driver: "authentik",
baseUrl: provider.baseUrl,
apiTokenRef: "THT_AUTHENTIK_API_TOKEN",
},
authorization: {
groupRoles: {
"fixture-users": ["user"],
"fixture-admin": ["admin"],
},
},
});
function backendEnvironment() {
return cleanBackendEnvironment({
NODE_ENV: "test",
HOST: "127.0.0.1",
PORT: String(backendPort),
PI_BIN: fakePi,
PI_CODING_AGENT_DIR: piAgentRoot,
THT_BIN: fakeTht,
THT_AUTH_STORAGE_BIN: authStorageBinary,
THT_HARNESS_DIR: harnessRoot,
THT_AUTH_CONFIG_FILE: authConfigFile,
THT_AUTH_STATE_ROOT: stateRoot,
THT_SECRETS_FILE: secretsFile,
NODE_EXTRA_CA_CERTS: provider.caFile,
SETTINGS_FILE: settingsFile,
THT_MAINTENANCE_FILE: maintenanceFile,
THT_WORKSPACE_REGISTRY_ROOT: registryRoot,
THT_WORKSPACE_SECRET_STORE_ROOT: workspaceSecretRoot,
THT_WORKSPACE_SECRET_RUNTIME_ROOT: workspaceRuntimeRoot,
THT_WORKSPACE_INSTALLATION_ID: "e2e",
THT_DATA_ROOT: join(root, "data"),
...(qdrant ? { THT_INTERNAL_QDRANT_URL: qdrant.baseUrl } : {}),
...(workspace ? {
THT_WORKSPACE_GIT_REMOTE: workspace.remote,
THT_WORKSPACE_GIT_BRANCH: "main",
THT_WORKSPACE_SECRET_ROOTS: fixtureSecretRoot,
THT_WS_FIXTURE_WORKSPACE_DWH_TRANSPORT: "postgres_direct",
THT_WS_FIXTURE_WORKSPACE_DWH_HOST: "127.0.0.1",
THT_WS_FIXTURE_WORKSPACE_DWH_PORT: "5432",
THT_WS_FIXTURE_WORKSPACE_DWH_USER: "fixture",
THT_WS_FIXTURE_WORKSPACE_DWH_PASSWORD_FILE: fixtureDwhPasswordFile,
THT_WS_FIXTURE_WORKSPACE_DWH_TLS_CA_FILE: fixtureDwhCaFile,
} : {}),
...memoryRuntime,
});
}
async function startBackend() {
if (mode === undefined) throw safeError("e2e_auth_mode_not_configured");
backend = managedProcess(join(backendRoot, "node_modules", ".bin", "tsx"), ["src/server.ts"], {
cwd: backendRoot,
env: backendEnvironment(),
});
await waitForOk(`${backendUrl}/health`, backend);
}
async function restartBackend() {
await backend?.close();
backend = undefined;
await startBackend();
}
return {
publicUrl,
fixtureWorkspaceRoot() {
if (!workspace) throw safeError("e2e_workspace_fixture_missing");
return join(registryRoot, "repo", workspace.id);
},
localAccount(account) {
const found = accounts[account];
if (!found) throw safeError("e2e_local_account_unknown");
return found;
},
lastAuthorization() {
return provider.lastAuthorization();
},
async providerSurface() {
const discovery = await providerJson(`${provider.issuer}.well-known/openid-configuration`, provider.caFile);
const jwks = await providerJson(`${provider.issuer}jwks`, provider.caFile);
const device = await providerJson(`${provider.issuer}device_authorization`, provider.caFile, {
method: "POST",
body: new URLSearchParams({
client_id: FIXTURE_CLIENT_ID,
client_secret: credentials.clientSecret,
}).toString(),
});
const deviceCode = typeof device.body?.device_code === "string" ? device.body.device_code : "";
const deviceToken = deviceCode.length === 0 ? { status: 0, body: {} } : await providerJson(`${provider.issuer}token`, provider.caFile, {
method: "POST",
body: new URLSearchParams({
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
client_id: FIXTURE_CLIENT_ID,
client_secret: credentials.clientSecret,
device_code: deviceCode,
}).toString(),
});
const groups = await providerJson(`${provider.baseUrl}/api/v3/core/groups/?name=fixture-users`, provider.caFile, {
headers: { authorization: `Bearer ${credentials.apiToken}` },
});
return {
discovery: discovery.status === 200 && discovery.body?.issuer === provider.issuer,
jwks: jwks.status === 200 && jwks.body?.keys?.[0]?.alg === "RS256" && jwks.body?.keys?.[0]?.use === "sig",
deviceAuthorization: device.status === 200 && typeof device.body?.device_code === "string"
&& typeof device.body?.verification_uri === "string",
deviceToken: deviceToken.status === 200 && typeof deviceToken.body?.id_token === "string",
groupList: groups.status === 200 && groups.body?.results?.[0]?.name === "fixture-users",
runtimeCredential: credentials.runtime,
};
},
setOidcIdentity(identity) {
provider.setIdentity(identity);
},
async useLocalMode() {
writeSecure(authConfigFile, JSON.stringify(localConfig()));
mode = "local";
await restartBackend();
},
async useOidcMode(identity, variant = "correct") {
if (!OIDC_CREDENTIAL_VARIANTS.has(variant)) throw safeError("e2e_oidc_credential_variant_invalid");
provider.setIdentity(identity);
writeOidcSecrets(variant);
writeSecure(authConfigFile, JSON.stringify(oidcConfig(variant)));
mode = "oidc";
await restartBackend();
},
async authDiagnostics() {
if (mode !== "oidc") throw safeError("e2e_oidc_mode_required");
return oneShotJson(
join(backendRoot, "node_modules", ".bin", "tsx"),
["src/auth/diagnostic-command.ts", "--json"],
{ cwd: backendRoot, env: backendEnvironment() },
);
},
restartBackend,
async close() {
await backend?.close();
await frontend.close();
await provider.close();
await qdrant?.close();
rmSync(root, { recursive: true, force: true });
},
};
} catch (error) {
await backend?.close();
await frontend.close();
await provider.close();
await qdrant?.close();
rmSync(root, { recursive: true, force: true });
throw error;
}
}