import { open, readFile, type FileHandle } from "node:fs/promises"; import { createHash } from "node:crypto"; import { join } from "node:path"; import { afterEach, expect, it, onTestFinished, vi } from "vitest"; const state = vi.hoisted(() => ({ readbackFailed: false, failReadback: true, rejectPublicationClose: false, publicationCloseFailure: undefined as unknown, publicationHandles: [] as FileHandle[], beforePublicationOpen: undefined as | ((path: string) => Promise) | undefined, rejectPublicationMode: false, rejectStageRemoval: false, beforeFirstPublication: undefined as (() => Promise) | undefined, afterPublication: undefined as ((path: string) => Promise) | undefined, rejectRestore: false, removeCopiedRecovery: false, afterBackupCopy: undefined as | ((source: string, destination: string) => Promise) | undefined, initialCleanupFailure: undefined as unknown, readbackCleanupFailure: undefined as unknown, })); afterEach(() => { state.readbackFailed = false; state.failReadback = true; state.rejectPublicationClose = false; state.publicationCloseFailure = undefined; state.publicationHandles = []; state.beforePublicationOpen = undefined; state.rejectPublicationMode = false; state.rejectStageRemoval = false; state.afterPublication = undefined; state.beforeFirstPublication = undefined; state.rejectRestore = false; state.removeCopiedRecovery = false; state.afterBackupCopy = undefined; state.initialCleanupFailure = undefined; state.readbackCleanupFailure = undefined; }); vi.mock("node:fs/promises", async (importOriginal) => { const original = await importOriginal(); return { ...original, open: async (...arguments_: Parameters) => { const writable = typeof arguments_[1] === "number" && (arguments_[1] & 3) === 2; if (writable && typeof arguments_[0] === "string") await state.beforePublicationOpen?.(arguments_[0]); const handle = await original.open(...arguments_); if (typeof arguments_[1] === "number" && (arguments_[1] & 3) === 2) { state.publicationHandles.push(handle); const write = handle.write.bind(handle); handle.write = ((...values: Parameters) => { if (state.rejectRestore && state.readbackFailed) return Promise.reject(new Error("injected restore write failure")); return write(...values); }) as typeof handle.write; const chmod = handle.chmod.bind(handle); handle.chmod = async (mode) => { if (state.rejectPublicationMode && mode === 0o400) throw new Error("staging mode preparation denied"); await chmod(mode); }; const close = handle.close.bind(handle); handle.close = async () => { if (state.rejectPublicationClose) throw ( state.publicationCloseFailure ?? new Error("injected publication close failure") ); await close(); }; } return handle; }, rmdir: async (...values: Parameters) => { if ( state.rejectStageRemoval && String(values[0]).includes(".rea-config-") ) throw new Error("injected staging directory removal failure"); return original.rmdir(...values); }, rename: async (...values: Parameters) => { const result = await original.rename(...values); state.readbackFailed = true; await state.afterPublication?.(String(values[1])); return result; }, link: async (...values: Parameters) => { await state.beforeFirstPublication?.(); const result = await original.link(...values); state.readbackFailed = true; await state.afterPublication?.(String(values[1])); return result; }, copyFile: async ( ...arguments_: Parameters ): Promise => { await original.copyFile(...arguments_); const source = arguments_[0]; const destination = arguments_[1]; if (typeof source === "string" && typeof destination === "string") await state.afterBackupCopy?.(source, destination); if ( state.removeCopiedRecovery && typeof destination === "string" && destination.endsWith(".recovery") ) await original.rm(destination); }, }; }); vi.mock("../../src/application/RegularFileRead.js", async (importOriginal) => { const original = await importOriginal< typeof import("../../src/application/RegularFileRead.js") >(); return { ...original, readRegularFileText: ( ...arguments_: Parameters ) => state.initialCleanupFailure !== undefined ? Promise.reject(state.initialCleanupFailure) : state.readbackFailed && state.readbackCleanupFailure !== undefined ? Promise.reject(state.readbackCleanupFailure) : state.readbackFailed && state.failReadback ? Promise.reject(new Error("injected readback failure")) : original.readRegularFileText(...arguments_), }; }); import { ClientConfigurationPublication } from "../../src/application/ClientConfigurationPublication.js"; import { configureClientConfiguration } from "../../src/application/SetupClientConfiguration.js"; import { RegularFileCleanupFailure } from "../../src/application/RegularFileRead.js"; import { OwnedFileHandle } from "../../src/filesystem/OwnedFileHandle.js"; import type { DoctorHost } from "../../src/application/Doctor.js"; import { systemSetupHost } from "../../src/application/SetupHost.js"; import { systemUninstallHost } from "../../src/application/Uninstall.js"; import { ConfigurationError } from "../../src/domain/configurationErrors.js"; import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js"; import { createDeferred } from "../fixtures/binarySession.js"; const ownReadHandle = async (path: string) => { const owner = new OwnedFileHandle(await open(path, "r")); onTestFinished(() => owner.close()); return owner; }; it("reports a failed config rollback and retains its backup path", async () => { state.readbackFailed = false; state.rejectRestore = false; state.initialCleanupFailure = undefined; state.readbackCleanupFailure = undefined; const root = await createTestTempDirectory("rea-config-rollback-"); const path = join(root, "client.json"); const original = '{"keep":true}\n'; await import("node:fs/promises").then(({ writeFile }) => writeFile(path, original), ); state.rejectRestore = true; const result = await configureClientConfiguration( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ); expect(result).toEqual({ status: "failed", reason: "rollback", backupPath: `${path}.rea.backup`, failure: { kind: "restore", path, detail: "injected restore write failure", }, }); expect(await readFile(`${path}.rea.backup`, "utf8")).toBe(original); expect(JSON.parse(await readFile(path, "utf8"))).toMatchObject({ mcpServers: { rea: { command: "rea", args: ["mcp"] } }, }); }); it("preserves the first backup and reports a verified backup of the current original", async () => { state.readbackFailed = false; state.rejectRestore = false; state.initialCleanupFailure = undefined; state.readbackCleanupFailure = undefined; const root = await createTestTempDirectory("rea-config-stale-backup-"); const path = join(root, "client.json"); const firstOriginal = '{"keep":"first"}\n'; const currentOriginal = '{"keep":"current"}\n'; await import("node:fs/promises").then(async ({ writeFile }) => { await writeFile(path, currentOriginal); await writeFile(`${path}.rea.backup`, firstOriginal); }); state.rejectRestore = true; const result = await configureClientConfiguration( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ); state.rejectRestore = false; const recoveryPath = `${path}.rea.backup.${createHash("sha256") .update(currentOriginal, "utf8") .digest("hex")}.recovery`; expect(result).toMatchObject({ status: "failed", reason: "rollback", backupPath: recoveryPath, failure: { kind: "restore", path, detail: "injected restore write failure", }, }); expect(await readFile(`${path}.rea.backup`, "utf8")).toBe(firstOriginal); expect(await readFile(recoveryPath, "utf8")).toBe(currentOriginal); }); it("does not write config when a recovery copy disappears before verification", async () => { state.readbackFailed = false; state.rejectRestore = false; state.removeCopiedRecovery = false; state.initialCleanupFailure = undefined; state.readbackCleanupFailure = undefined; const root = await createTestTempDirectory("rea-config-vanished-recovery-"); const path = join(root, "client.json"); const firstOriginal = '{"keep":"first"}\n'; const currentOriginal = '{"keep":"current"}\n'; await import("node:fs/promises").then(async ({ writeFile }) => { await writeFile(path, currentOriginal); await writeFile(`${path}.rea.backup`, firstOriginal); }); state.removeCopiedRecovery = true; const result = await configureClientConfiguration( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ); state.removeCopiedRecovery = false; expect(result).toEqual({ status: "failed", reason: "backup" }); expect(await readFile(path, "utf8")).toBe(currentOriginal); expect(await readFile(`${path}.rea.backup`, "utf8")).toBe(firstOriginal); }); it("does not roll back or remove config when its initial read cleanup fails", async () => { state.readbackFailed = false; state.rejectRestore = false; state.readbackCleanupFailure = undefined; const root = await createTestTempDirectory("rea-config-initial-cleanup-"); const path = join(root, "client.json"); const original = '{"keep":true}\n'; await import("node:fs/promises").then(({ writeFile }) => writeFile(path, original), ); const failure = new RegularFileCleanupFailure( path, await ownReadHandle(path), { kind: "completed", value: original }, new Error("injected initial close failure"), ); state.initialCleanupFailure = failure; await expect( configureClientConfiguration( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ), ).rejects.toBe(failure); expect(await readFile(path, "utf8")).toBe(original); }); it("retains first-run configuration for manual recovery without deleting by pathname", async () => { state.readbackFailed = false; state.rejectRestore = false; state.initialCleanupFailure = undefined; state.readbackCleanupFailure = undefined; const root = await createTestTempDirectory("rea-config-first-run-rollback-"); const path = join(root, "client.json"); const result = await configureClientConfiguration( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ); expect(result).toMatchObject({ status: "failed", reason: "rollback", failure: { kind: "restore", path, detail: "First-run configuration recovery requires manual removal; pathname deletion cannot prove ownership", }, }); expect(result).not.toHaveProperty("backupPath"); expect(JSON.parse(await readFile(path, "utf8"))).toMatchObject({ mcpServers: { rea: { command: "rea", args: ["mcp"] } }, }); }); it("retains readback cleanup ownership when rollback also fails", async () => { state.readbackFailed = false; state.rejectRestore = false; state.initialCleanupFailure = undefined; const root = await createTestTempDirectory("rea-config-readback-cleanup-"); const path = join(root, "client.json"); const original = '{"keep":true}\n'; await import("node:fs/promises").then(({ writeFile }) => writeFile(path, original), ); const owner = await ownReadHandle(path); const cleanupFailure = new RegularFileCleanupFailure( path, owner, { kind: "completed", value: "" }, new Error("injected readback close failure"), ); state.readbackCleanupFailure = cleanupFailure; state.rejectRestore = true; const host = systemSetupHost( { platform: "linux", homeDirectory: root } as DoctorHost, { HOME: root }, ); let caught: unknown; try { await host.configureClient( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ); } catch (cause: unknown) { caught = cause; } finally { await host.close?.(); } if (!(caught instanceof ConfigurationError)) throw caught; expect(caught.message).toContain("injected restore write failure"); expect(caught.message).toContain(`${path}.rea.backup`); const cleanupOwnerError = caught.cause; if (!(cleanupOwnerError instanceof RegularFileCleanupFailure)) throw new Error("Configuration failure lost its read owner"); expect(cleanupOwnerError).toMatchObject({ path, outcome: { kind: "completed", value: "" }, cleanupCause: expect.any(AggregateError), }); expect(cleanupOwnerError.owner).toBe(owner); expect(await readFile(`${path}.rea.backup`, "utf8")).toBe(original); }); it.each(["existing", "first-run"] as const)( "retains a replacement symlink and its target when %s readback fails", async (kind) => { const root = await createTestTempDirectory("rea-config-replaced-rollback-"); const path = join(root, "client.json"); const replacement = join(root, "replacement.json"); const original = '{"keep":"original"}\n'; const foreign = '{"keep":"replacement"}\n'; const fs = await vi.importActual( "node:fs/promises", ); await fs.writeFile(replacement, foreign); if (kind === "existing") await fs.writeFile(path, original); state.afterPublication = async (publishedPath) => { await fs.unlink(publishedPath); await fs.symlink(replacement, publishedPath); }; const result = await configureClientConfiguration( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ); expect(await fs.readFile(replacement, "utf8")).toBe(foreign); expect((await fs.lstat(path)).isSymbolicLink()).toBe(true); expect(result).toMatchObject({ status: "failed", reason: "rollback" }); if (kind === "existing") expect(await fs.readFile(`${path}.rea.backup`, "utf8")).toBe(original); }, ); it("retains a replacement regular file when readback fails", async () => { const root = await createTestTempDirectory("rea-config-replaced-file-"); const path = join(root, "client.json"); const replacement = join(root, "replacement.json"); const original = '{"keep":"original"}\n'; const foreign = '{"keep":"replacement"}\n'; const fs = await vi.importActual( "node:fs/promises", ); await fs.writeFile(path, original); await fs.writeFile(replacement, foreign); state.afterPublication = (publishedPath) => fs.rename(replacement, publishedPath); const result = await configureClientConfiguration( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ); expect(await fs.readFile(path, "utf8")).toBe(foreign); expect(result).toMatchObject({ status: "failed", reason: "rollback" }); expect(await fs.readFile(`${path}.rea.backup`, "utf8")).toBe(original); }); it("retains and retries both readback and publication descriptors after mixed close failures", async () => { const root = await createTestTempDirectory("rea-config-two-close-owners-"); const path = join(root, "client.json"); const fs = await vi.importActual( "node:fs/promises", ); await fs.writeFile(path, '{"keep":true}\n'); const readOwner = await ownReadHandle(path); const closeRead = readOwner.handle.close.bind(readOwner.handle); let rejectReadClose = true; readOwner.handle.close = async () => { if (rejectReadClose) throw new Error("injected readback close failure"); await closeRead(); }; state.readbackCleanupFailure = new RegularFileCleanupFailure( path, readOwner, { kind: "completed", value: "" }, new Error("injected readback close failure"), ); state.rejectPublicationClose = true; const host = systemSetupHost( { platform: "linux", homeDirectory: root } as DoctorHost, { HOME: root }, ); try { await expect( host.configureClient( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ), ).rejects.toBeInstanceOf(ConfigurationError); expect(state.publicationHandles).toHaveLength(1); expect(readOwner.handle.fd).toBeGreaterThanOrEqual(0); expect(state.publicationHandles[0]!.fd).toBeGreaterThanOrEqual(0); rejectReadClose = false; state.rejectPublicationClose = false; expect(await host.close?.()).toBeUndefined(); expect(readOwner.handle.fd).toBe(-1); expect(state.publicationHandles[0]!.fd).toBe(-1); } finally { rejectReadClose = false; state.rejectPublicationClose = false; await host.close?.(); await readOwner.close(); for (const handle of state.publicationHandles) if (handle.fd >= 0) await handle.close(); } }); it("rejects a replaced publication even when the replacement passes registration readback", async () => { const root = await createTestTempDirectory("rea-config-valid-replacement-"); const path = join(root, "client.json"); const replacement = join(root, "replacement.json"); const fs = await vi.importActual( "node:fs/promises", ); const original = '{"keep":true}\n'; await fs.writeFile(path, original); let foreign = ""; state.failReadback = false; state.afterPublication = async (publishedPath) => { foreign = await fs.readFile(publishedPath, "utf8"); await fs.writeFile(replacement, foreign); await fs.rename(replacement, publishedPath); }; expect( await configureClientConfiguration( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ), ).toMatchObject({ status: "failed", reason: "rollback" }); expect(await fs.readFile(path, "utf8")).toBe(foreign); expect(await fs.readFile(`${path}.rea.backup`, "utf8")).toBe(original); }); it("retains concurrent edits to the published inode rather than overwriting them", async () => { const root = await createTestTempDirectory("rea-config-inode-edit-"); const path = join(root, "client.json"); const fs = await vi.importActual( "node:fs/promises", ); const original = '{"keep":true}\n'; const edited = '{"keep":"concurrent edit"}\n'; await fs.writeFile(path, original); state.afterPublication = (publishedPath) => fs.writeFile(publishedPath, edited); expect( await configureClientConfiguration( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ), ).toMatchObject({ status: "failed", reason: "rollback" }); expect(await fs.readFile(path, "utf8")).toBe(edited); expect(await fs.readFile(`${path}.rea.backup`, "utf8")).toBe(original); }); it("does not adopt a descriptor named by an opaque publication close rejection", async () => { const root = await createTestTempDirectory( "rea-config-close-cause-ownership-", ); const path = join(root, "client.json"); const unrelated = join(root, "unrelated.json"); const fs = await vi.importActual( "node:fs/promises", ); await fs.writeFile(path, '{"keep":true}\n'); await fs.writeFile(unrelated, "unrelated"); const foreignOwner = await ownReadHandle(unrelated); state.failReadback = false; state.rejectPublicationClose = true; state.publicationCloseFailure = new RegularFileCleanupFailure( unrelated, foreignOwner, { kind: "completed", value: "unrelated" }, new Error("opaque close rejection"), ); const host = systemSetupHost( { platform: "linux", homeDirectory: root } as DoctorHost, { HOME: root }, ); try { await expect( host.configureClient( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ), ).rejects.toBeInstanceOf(ConfigurationError); state.rejectPublicationClose = false; expect(await host.close?.()).toBeUndefined(); expect(state.publicationHandles[0]!.fd).toBe(-1); expect(foreignOwner.handle.fd).toBeGreaterThanOrEqual(0); } finally { state.rejectPublicationClose = false; await host.close?.(); await foreignOwner.close(); for (const handle of state.publicationHandles) if (handle.fd >= 0) await handle.close(); } }); it.each(["symlink", "regular", "valid-readback"] as const)( "uninstall retains a %s replacement instead of restoring through its name", async (kind) => { const root = await createTestTempDirectory( "rea-uninstall-replaced-rollback-", ); const path = join(root, "client.json"); const replacement = join(root, "replacement.json"); const fs = await vi.importActual( "node:fs/promises", ); const original = '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}},"keep":"original"}\n'; const foreign = '{"keep":"replacement"}\n'; await fs.writeFile(path, original); await fs.writeFile(replacement, foreign); state.failReadback = kind !== "valid-readback"; state.afterPublication = async (publishedPath) => { state.afterPublication = undefined; if (kind === "symlink") { await fs.unlink(publishedPath); await fs.symlink(replacement, publishedPath); } else await fs.rename(replacement, publishedPath); }; const host = systemUninstallHost(root); try { const result = await host.removeClient({ name: "cursor", configPath: path, format: "json", }); expect(await fs.readFile(path, "utf8")).toBe(foreign); if (kind === "symlink") { expect((await fs.lstat(path)).isSymbolicLink()).toBe(true); expect(await fs.readFile(replacement, "utf8")).toBe(foreign); } expect(result.status).toBe("failed"); expect(result.detail).not.toContain("The original was restored"); expect(await fs.readFile(`${path}.rea.backup`, "utf8")).toBe(original); } finally { await host.close?.(); } }, ); it("uninstall retains both readback and publication owners until cleanup retry", async () => { const root = await createTestTempDirectory("rea-uninstall-two-close-owners-"); const path = join(root, "client.json"); const fs = await vi.importActual( "node:fs/promises", ); await fs.writeFile( path, '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}}}\n', ); const readOwner = await ownReadHandle(path); const closeRead = readOwner.handle.close.bind(readOwner.handle); let rejectReadClose = true; readOwner.handle.close = async () => { if (rejectReadClose) throw new Error("injected readback close failure"); await closeRead(); }; state.readbackCleanupFailure = new RegularFileCleanupFailure( path, readOwner, { kind: "completed", value: "" }, new Error("readback close failed"), ); state.rejectPublicationClose = true; const host = systemUninstallHost(root); try { const result = await host.removeClient({ name: "cursor", configPath: path, format: "json", }); expect(result.status).toBe("failed"); expect(state.publicationHandles).toHaveLength(1); expect(readOwner.handle.fd).toBeGreaterThanOrEqual(0); expect(state.publicationHandles[0]!.fd).toBeGreaterThanOrEqual(0); rejectReadClose = false; state.rejectPublicationClose = false; expect(await host.close?.()).toBeUndefined(); expect(readOwner.handle.fd).toBe(-1); expect(state.publicationHandles[0]!.fd).toBe(-1); } finally { rejectReadClose = false; state.rejectPublicationClose = false; await host.close?.(); await readOwner.close(); for (const handle of state.publicationHandles) if (handle.fd >= 0) await handle.close(); } }); it("uninstall retains the exact owner of a failed initial config read", async () => { const root = await createTestTempDirectory( "rea-uninstall-initial-read-owner-", ); const path = join(root, "client.json"); const fs = await vi.importActual( "node:fs/promises", ); const original = '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}}}\n'; await fs.writeFile(path, original); const owner = await ownReadHandle(path); const close = owner.handle.close.bind(owner.handle); let rejectClose = true; owner.handle.close = async () => { if (rejectClose) throw new Error("initial close denied"); await close(); }; state.initialCleanupFailure = new RegularFileCleanupFailure( path, owner, { kind: "completed", value: original }, new Error("initial close denied"), ); const host = systemUninstallHost(root); try { expect( await host.inspectClient({ name: "cursor", configPath: path, format: "json", }), ).toMatchObject({ status: "failed" }); expect(state.publicationHandles).toHaveLength(0); expect(owner.handle.fd).toBeGreaterThanOrEqual(0); rejectClose = false; expect(await host.close?.()).toBeUndefined(); expect(owner.handle.fd).toBe(-1); expect(await fs.readFile(path, "utf8")).toBe(original); await expect(fs.readFile(`${path}.rea.backup`)).rejects.toMatchObject({ code: "ENOENT", }); } finally { rejectClose = false; await host.close?.(); await owner.close(); } }); it("uninstall close joins a publication already admitted by its host", async () => { const root = await createTestTempDirectory( "rea-uninstall-close-publication-", ); const path = join(root, "client.json"); const fs = await vi.importActual( "node:fs/promises", ); await fs.writeFile( path, '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}}}\n', ); const arrived = createDeferred(); const release = createDeferred(); state.afterPublication = async () => { arrived.resolve(); await release.promise; }; const host = systemUninstallHost(root); const removal = host.removeClient({ name: "cursor", configPath: path, format: "json", }); await arrived.promise; let closed = false; const closing = host.close?.().then((result) => { closed = true; return result; }); try { await new Promise((resolve) => setImmediate(resolve)); expect(closed).toBe(false); expect(state.publicationHandles[0]!.fd).toBeGreaterThanOrEqual(0); } finally { release.resolve(); } expect((await removal).status).toBe("failed"); expect(await closing).toBeUndefined(); expect(state.publicationHandles[0]!.fd).toBe(-1); await expect( host.removeClient({ name: "cursor", configPath: path, format: "json" }), ).rejects.toThrow("closed"); }); it("cleanup retry never adopts an opaque cancellation reason as another owner", async () => { const root = await createTestTempDirectory( "rea-cleanup-cancellation-authority-", ); const path = join(root, "owned.json"); const other = join(root, "other.json"); const fs = await vi.importActual( "node:fs/promises", ); await fs.writeFile(path, "owned"); await fs.writeFile(other, "other"); const owned = await ownReadHandle(path); const foreign = await ownReadHandle(other); const opaque = new RegularFileCleanupFailure( other, foreign, { kind: "completed", value: "other" }, new Error("opaque reason"), ); const { RegularFileOperationCancelled } = await import("../../src/filesystem/RegularFile.js"); const { ArtifactResourceScope } = await import("../../src/artifacts/ArtifactResourceScope.js"); const { retryRegularFileCleanupFailures } = await import("../../src/application/RegularFileRead.js"); const resources = new ArtifactResourceScope(); const outcome = await retryRegularFileCleanupFailures( new RegularFileCleanupFailure( path, owned, { kind: "failed", cause: new RegularFileOperationCancelled(opaque) }, new Error("owned close failed"), ), resources, ); expect(outcome.primary).toBe(opaque); expect(outcome.failures).toHaveLength(1); expect(owned.handle.fd).toBe(-1); await resources.close(); expect(foreign.handle.fd).toBeGreaterThanOrEqual(0); }); it("uninstall progresses when a peer leaves a denied-close descriptor pending", async () => { const root = await createTestTempDirectory("rea-uninstall-peer-cleanup-"); const path = join(root, "client.json"); const other = join(root, "other.json"); const fs = await vi.importActual( "node:fs/promises", ); await fs.writeFile( path, '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}}}\n', ); await fs.writeFile(other, "{}"); const owner = await ownReadHandle(other); const close = owner.handle.close.bind(owner.handle); let rejectClose = true; owner.handle.close = async () => { if (rejectClose) throw new Error("peer close denied"); await close(); }; const arrived = createDeferred(); const release = createDeferred(); state.failReadback = false; state.afterPublication = async () => { arrived.resolve(); await release.promise; }; const host = systemUninstallHost(root); const removal = host.removeClient({ name: "cursor", configPath: path, format: "json", }); let timeout: ReturnType | undefined; try { await arrived.promise; state.initialCleanupFailure = new RegularFileCleanupFailure( other, owner, { kind: "completed", value: "{}" }, new Error("peer close denied"), ); expect( await host.inspectClient({ name: "other", configPath: other, format: "json", }), ).toMatchObject({ status: "failed" }); state.initialCleanupFailure = undefined; release.resolve(); const result = await Promise.race([ removal, new Promise((_resolve, reject) => { timeout = setTimeout( () => reject( new Error("configuration admission deadlocked with file cleanup"), ), 5000, ); }), ]); expect(result.status).toBe("failed"); expect(owner.handle.fd).toBeGreaterThanOrEqual(0); rejectClose = false; expect(await host.close?.()).toBeUndefined(); expect(owner.handle.fd).toBe(-1); expect(state.publicationHandles[0]!.fd).toBe(-1); } finally { if (timeout !== undefined) clearTimeout(timeout); state.initialCleanupFailure = undefined; rejectClose = false; release.resolve(); await owner.close(); for (const handle of state.publicationHandles) if (handle.fd >= 0) await handle.close(); } }); it.skipIf(process.platform === "win32" || process.getuid?.() === 0)( "exclusive staging refuses a preexisting entry without changing its permissions", async () => { const root = await createTestTempDirectory( "rea-uninstall-staging-exclusive-", ); const path = join(root, "client.json"); const foreign = join(root, "foreign.json"); const fs = await vi.importActual( "node:fs/promises", ); const original = '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}}}\n'; await fs.writeFile(path, original); await fs.chmod(path, 0o400); await fs.writeFile(foreign, "foreign"); await fs.chmod(foreign, 0o640); let inserted: string | undefined; state.beforePublicationOpen = async (stagedPath) => { state.beforePublicationOpen = undefined; inserted = stagedPath; await fs.link(foreign, stagedPath); }; const host = systemUninstallHost(root); try { expect( ( await host.removeClient({ name: "cursor", configPath: path, format: "json", }) ).status, ).toBe("failed"); expect(await fs.readFile(foreign, "utf8")).toBe("foreign"); expect((await fs.lstat(foreign)).mode & 0o777).toBe(0o640); expect(await fs.readFile(path, "utf8")).toBe(original); expect((await fs.lstat(path)).mode & 0o777).toBe(0o400); expect(state.publicationHandles).toHaveLength(0); expect(await host.close?.()).toContain("cleanup failed"); } finally { if (inserted !== undefined) await fs.unlink(inserted); await host.close?.(); } }, ); it.skipIf(process.platform === "win32" || process.getuid?.() === 0)( "uninstall retains the private stage after mode preparation and descriptor cleanup fail", async () => { const root = await createTestTempDirectory( "rea-uninstall-staging-mode-retry-", ); const path = join(root, "client.json"); const fs = await vi.importActual( "node:fs/promises", ); const original = '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}}}\n'; await fs.writeFile(path, original); await fs.chmod(path, 0o400); state.rejectPublicationMode = true; state.rejectPublicationClose = true; const host = systemUninstallHost(root); try { expect( ( await host.removeClient({ name: "cursor", configPath: path, format: "json", }) ).status, ).toBe("failed"); expect(state.publicationHandles).toHaveLength(1); expect(state.publicationHandles[0]!.fd).toBeGreaterThanOrEqual(0); expect(await host.close?.()).toContain("cleanup failed"); state.rejectPublicationMode = false; state.rejectPublicationClose = false; expect(await host.close?.()).toBeUndefined(); expect(state.publicationHandles[0]!.fd).toBe(-1); expect(await fs.readFile(path, "utf8")).toBe(original); expect((await fs.lstat(path)).mode & 0o777).toBe(0o400); } finally { state.rejectPublicationMode = false; state.rejectPublicationClose = false; await host.close?.(); for (const handle of state.publicationHandles) if (handle.fd >= 0) await handle.close(); } }, ); it.skipIf(process.platform === "win32" || process.getuid?.() === 0)( "uninstall restores a read-only configuration through its retained writable descriptor", async () => { const root = await createTestTempDirectory( "rea-uninstall-readonly-restore-", ); const path = join(root, "client.json"); const fs = await vi.importActual( "node:fs/promises", ); const original = '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}},"keep":true}\n'; await fs.writeFile(path, original); await fs.chmod(path, 0o400); const host = systemUninstallHost(root); try { const result = await host.removeClient({ name: "cursor", configPath: path, format: "json", }); expect(result.status).toBe("failed"); expect(result.detail).toContain("The original was restored"); expect(await fs.readFile(path, "utf8")).toBe(original); expect((await fs.lstat(path)).mode & 0o777).toBe(0o400); expect(await fs.readFile(`${path}.rea.backup`, "utf8")).toBe(original); expect(state.publicationHandles[0]!.fd).toBe(-1); } finally { await host.close?.(); } }, ); it.each(["setup", "uninstall"] as const)( "%s refuses a configuration symlink inserted after backup without overwriting its target", async (operation) => { const root = await createTestTempDirectory( "rea-config-prepublication-symlink-", ); const path = join(root, "client.json"); const foreign = join(root, "foreign.json"); const fs = await vi.importActual( "node:fs/promises", ); const original = operation === "setup" ? '{"keep":"original"}\n' : '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}},"keep":"original"}\n'; const unrelated = '{"keep":"foreign"}\n'; await fs.writeFile(path, original); await fs.writeFile(foreign, unrelated); state.failReadback = false; state.afterBackupCopy = async (source) => { state.afterBackupCopy = undefined; await fs.unlink(source); await fs.symlink(foreign, source); }; if (operation === "setup") { expect( ( await configureClientConfiguration( { name: "cursor", configPath: path, format: "json" }, {}, ["rea", "mcp"], ) ).status, ).toBe("failed"); } else { const host = systemUninstallHost(root); try { expect( ( await host.removeClient({ name: "cursor", configPath: path, format: "json", }) ).status, ).toBe("failed"); } finally { await host.close?.(); } } expect(await fs.readFile(foreign, "utf8")).toBe(unrelated); expect((await fs.lstat(path)).isSymbolicLink()).toBe(true); expect(await fs.readFile(`${path}.rea.backup`, "utf8")).toBe(original); }, ); const runConfigurationChange = async ( operation: "setup" | "uninstall", root: string, path: string, ) => { if (operation === "setup") return configureClientConfiguration( { name: "cursor", configPath: path, format: "json" }, {}, ["new-rea", "mcp"], ); const host = systemUninstallHost(root); try { return await host.removeClient({ name: "cursor", configPath: path, format: "json", }); } finally { await host.close?.(); } }; it.each([ ["setup", "edit"], ["uninstall", "edit"], ["setup", "delete"], ["uninstall", "delete"], ] as const)( "%s preserves a configuration %s after backup", async (operation, change) => { const root = await createTestTempDirectory("rea-config-backup-change-"); const path = join(root, "client.json"); const fs = await vi.importActual( "node:fs/promises", ); const original = '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}},"keep":true}\n'; const edited = '{"changed":"by client"}\n'; await fs.writeFile(path, original); state.failReadback = false; state.afterBackupCopy = async (source) => { state.afterBackupCopy = undefined; if (change === "edit") await fs.writeFile(source, edited); else await fs.unlink(source); }; expect((await runConfigurationChange(operation, root, path)).status).toBe( "failed", ); if (change === "edit") expect(await fs.readFile(path, "utf8")).toBe(edited); else await expect(fs.lstat(path)).rejects.toMatchObject({ code: "ENOENT" }); expect(await fs.readFile(`${path}.rea.backup`, "utf8")).toBe(original); expect( (await fs.readdir(root)).filter((name) => name.startsWith(".rea-config-"), ), ).toEqual([]); }, ); it.each(["setup", "uninstall"] as const)( "%s refuses an input changed during staging", async (operation) => { const root = await createTestTempDirectory("rea-config-staging-change-"); const path = join(root, "client.json"); const fs = await vi.importActual( "node:fs/promises", ); const original = '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}},"keep":true}\n'; const edited = '{"changed":"during staging"}\n'; await fs.writeFile(path, original); state.failReadback = false; state.beforePublicationOpen = async () => { state.beforePublicationOpen = undefined; await fs.writeFile(path, edited); }; expect((await runConfigurationChange(operation, root, path)).status).toBe( "failed", ); expect(await fs.readFile(path, "utf8")).toBe(edited); expect(await fs.readFile(`${path}.rea.backup`, "utf8")).toBe(original); expect( (await fs.readdir(root)).filter((name) => name.startsWith(".rea-config-"), ), ).toEqual([]); }, ); it("first-run setup preserves a destination created during staging", async () => { const root = await createTestTempDirectory("rea-config-first-conflict-"); const path = join(root, "client.json"); const fs = await vi.importActual( "node:fs/promises", ); const edited = '{"created":"by client"}\n'; state.failReadback = false; state.beforePublicationOpen = async () => { state.beforePublicationOpen = undefined; await fs.writeFile(path, edited); }; expect((await runConfigurationChange("setup", root, path)).status).toBe( "failed", ); expect(await fs.readFile(path, "utf8")).toBe(edited); expect( (await fs.readdir(root)).filter((name) => name.startsWith(".rea-config-")), ).toEqual([]); }); it.each(["setup", "uninstall"] as const)( "%s retains staging directory cleanup for host retry", async (operation) => { const root = await createTestTempDirectory( "rea-config-staging-directory-retry-", ); const path = join(root, "client.json"); const fs = await vi.importActual( "node:fs/promises", ); await fs.writeFile( path, '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}},"keep":true}\n', ); state.failReadback = false; state.rejectStageRemoval = true; const host = operation === "setup" ? systemSetupHost( { platform: "linux", homeDirectory: root } as DoctorHost, { HOME: root }, ) : systemUninstallHost(root); try { const client = { name: "cursor", configPath: path, format: "json" as const, }; if (operation === "setup") { await expect( (host as ReturnType).configureClient( client, {}, ["new-rea", "mcp"], ), ).rejects.toThrow(); } else { expect( ( await (host as ReturnType).removeClient( client, ) ).status, ).toBe("failed"); } expect(await host.close?.()).toContain("cleanup"); expect( (await fs.readdir(root)).filter((name) => name.startsWith(".rea-config-"), ), ).toHaveLength(1); state.rejectStageRemoval = false; expect(await host.close?.()).toBeUndefined(); expect( (await fs.readdir(root)).filter((name) => name.startsWith(".rea-config-"), ), ).toEqual([]); expect(state.publicationHandles[0]!.fd).toBe(-1); } finally { state.rejectStageRemoval = false; await host.close?.(); } }, ); it("first-run publication exclusively refuses a destination created after its final check", async () => { const root = await createTestTempDirectory("rea-config-exclusive-link-"); const path = join(root, "client.json"); const fs = await vi.importActual( "node:fs/promises", ); const edited = '{"created":"after final check"}\n'; state.beforeFirstPublication = async () => { await fs.writeFile(path, edited); }; await expect( ClientConfigurationPublication.write(path, "{}", undefined), ).rejects.toMatchObject({ code: "EEXIST" }); expect(await fs.readFile(path, "utf8")).toBe(edited); expect( (await fs.readdir(root)).filter((name) => name.startsWith(".rea-config-")), ).toEqual([]); }); it("publication keeps its selected canonical parent when a directory symlink is retargeted", async () => { const root = await createTestTempDirectory("rea-config-parent-selection-"); const fs = await vi.importActual( "node:fs/promises", ); const selected = join(root, "selected"); const foreign = join(root, "foreign"); const alias = join(root, "alias"); await fs.mkdir(selected); await fs.mkdir(foreign); await fs.symlink(selected, alias, "dir"); const original = '{"original":true}\n'; const unrelated = '{"foreign":true}\n'; await fs.writeFile(join(selected, "client.json"), original); await fs.writeFile(join(foreign, "client.json"), unrelated); state.beforePublicationOpen = async () => { state.beforePublicationOpen = undefined; await fs.unlink(alias); await fs.symlink(foreign, alias, "dir"); }; const publication = await ClientConfigurationPublication.write( join(alias, "client.json"), "{}", original, ); await publication.verify(() => publication.assertCurrent()); expect(await fs.readFile(join(selected, "client.json"), "utf8")).toBe("{}"); expect(await fs.readFile(join(foreign, "client.json"), "utf8")).toBe( unrelated, ); expect( (await fs.readdir(selected)).filter((name) => name.startsWith(".rea-config-"), ), ).toEqual([]); }); it.each(["setup", "uninstall"] as const)( "%s reads back the selected canonical parent after its alias is retargeted", async (operation) => { const root = await createTestTempDirectory( "rea-config-adapter-parent-selection-", ); const fs = await vi.importActual( "node:fs/promises", ); const selected = join(root, "selected"); const foreign = join(root, "foreign"); const alias = join(root, "alias"); await fs.mkdir(selected); await fs.mkdir(foreign); await fs.symlink(selected, alias, "dir"); const original = '{"mcpServers":{"rea":{"command":"rea","args":["mcp"]}},"keep":true}\n'; const unrelated = '{"foreign":true}\n'; await fs.writeFile(join(selected, "client.json"), original); await fs.writeFile(join(foreign, "client.json"), unrelated); state.failReadback = false; state.beforePublicationOpen = async () => { state.beforePublicationOpen = undefined; await fs.unlink(alias); await fs.symlink(foreign, alias, "dir"); }; expect( ( await runConfigurationChange( operation, root, join(alias, "client.json"), ) ).status, ).toBe(operation === "setup" ? "configured" : "removed"); expect( JSON.parse(await fs.readFile(join(selected, "client.json"), "utf8")), ).toMatchObject({ keep: true }); expect(await fs.readFile(join(foreign, "client.json"), "utf8")).toBe( unrelated, ); expect( await fs.readFile(join(selected, "client.json.rea.backup"), "utf8"), ).toBe(original); }, );