From 4763bc9d04f2845cc170799e4b4b08b2a812b857 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Thu, 26 Mar 2026 21:26:53 +0000 Subject: [PATCH] Delete useless tests --- .../src/tests/14-write-write-conflict.test.ts | 24 ++++ ...reate-update-coalesce-server-pause.test.ts | 25 ++++ .../16-create-during-reconciliation.test.ts | 50 +++++++ ...inary-pending-create-not-displaced.test.ts | 67 ---------- ...sce-update-remote-update-data-loss.test.ts | 97 -------------- ...esced-remote-update-watermark-loss.test.ts | 85 ------------ ...urrent-binary-create-deconfliction.test.ts | 77 ----------- .../concurrent-create-same-path-merge.test.ts | 60 --------- ...urrent-delete-during-remote-update.test.ts | 49 ------- .../tests/concurrent-delete-update.test.ts | 48 ------- ...oncurrent-edit-exact-same-position.test.ts | 91 ------------- ...urrent-rename-and-create-at-target.test.ts | 90 ------------- .../concurrent-rename-same-target.test.ts | 65 ---------- ...concurrent-update-diff-consistency.test.ts | 66 ---------- .../src/tests/create-delete-noop.test.ts | 29 ----- .../create-during-reconciliation.test.ts | 93 ------------- ...te-rename-create-same-path-offline.test.ts | 83 ------------ ...reate-update-coalesce-server-pause.test.ts | 50 ------- .../src/tests/delete-nonexistent-file.test.ts | 27 ---- .../src/tests/duplicate-content-files.test.ts | 41 ------ .../src/tests/empty-file-sync.test.ts | 49 ------- .../offline-multi-update-catchup.test.ts | 69 ---------- .../offline-operations-both-clients.test.ts | 43 ------ ...ne-rename-both-clients-same-source.test.ts | 84 ------------ .../offline-rename-pending-create.test.ts | 68 ---------- ...reconcile-pending-at-occupied-path.test.ts | 92 ------------- ...delete-coalesce-loses-local-update.test.ts | 86 ------------ .../rename-empty-file-loses-identity.test.ts | 78 ----------- .../src/tests/rename-nested-path.test.ts | 53 -------- ...e-tracked-to-occupied-pending-path.test.ts | 91 ------------- .../server-pause-concurrent-creates.test.ts | 88 ------------- .../server-pause-rename-propagation.test.ts | 73 ----------- .../src/tests/server-pause-resume.test.ts | 39 ------ ...stale-doc-orphan-duplicate-content.test.ts | 122 ------------------ .../tests/three-client-convergence.test.ts | 53 -------- .../update-vs-remote-delete-data-loss.test.ts | 85 ------------ ...ser-parenthesized-file-not-deleted.test.ts | 60 --------- .../src/tests/write-write-conflict.test.ts | 40 ------ 38 files changed, 99 insertions(+), 2391 deletions(-) create mode 100644 frontend/deterministic-tests/src/tests/14-write-write-conflict.test.ts create mode 100644 frontend/deterministic-tests/src/tests/15-create-update-coalesce-server-pause.test.ts create mode 100644 frontend/deterministic-tests/src/tests/16-create-during-reconciliation.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/binary-pending-create-not-displaced.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/coalesce-update-remote-update-data-loss.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/coalesced-remote-update-watermark-loss.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/concurrent-binary-create-deconfliction.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/concurrent-create-same-path-merge.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/concurrent-delete-during-remote-update.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/concurrent-delete-update.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/concurrent-edit-exact-same-position.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/concurrent-rename-and-create-at-target.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/concurrent-rename-same-target.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/concurrent-update-diff-consistency.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/create-delete-noop.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/create-during-reconciliation.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/create-rename-create-same-path-offline.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/create-update-coalesce-server-pause.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/delete-nonexistent-file.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/duplicate-content-files.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/empty-file-sync.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/offline-multi-update-catchup.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/offline-operations-both-clients.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/offline-rename-both-clients-same-source.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/offline-rename-pending-create.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/reconcile-pending-at-occupied-path.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/remote-delete-coalesce-loses-local-update.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/rename-empty-file-loses-identity.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/rename-nested-path.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/rename-tracked-to-occupied-pending-path.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/server-pause-concurrent-creates.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/server-pause-rename-propagation.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/server-pause-resume.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/stale-doc-orphan-duplicate-content.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/three-client-convergence.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/update-vs-remote-delete-data-loss.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/user-parenthesized-file-not-deleted.test.ts delete mode 100644 frontend/deterministic-tests/src/tests/write-write-conflict.test.ts diff --git a/frontend/deterministic-tests/src/tests/14-write-write-conflict.test.ts b/frontend/deterministic-tests/src/tests/14-write-write-conflict.test.ts new file mode 100644 index 00000000..f51370a6 --- /dev/null +++ b/frontend/deterministic-tests/src/tests/14-write-write-conflict.test.ts @@ -0,0 +1,24 @@ +import type { TestDefinition } from "../test-definition"; + +export const writeWriteConflictTest: TestDefinition = { + name: "Write/Write Conflict", + description: + "Two clients simultaneously create the same file with different content. " + + "Both contributions should be preserved in the merged result without duplication.", + clients: 2, + steps: [ + { type: "create", client: 0, path: "A.md", content: "hello" }, + { type: "create", client: 1, path: "A.md", content: "hello" }, + { type: "enable-sync", client: 0 }, + { type: "enable-sync", client: 1 }, + { type: "barrier" }, + { + type: "assert-consistent", + verify: (state) => { + state + .assertFileCount(1) + .assertContent("A.md", "hello") + } + } + ] +}; diff --git a/frontend/deterministic-tests/src/tests/15-create-update-coalesce-server-pause.test.ts b/frontend/deterministic-tests/src/tests/15-create-update-coalesce-server-pause.test.ts new file mode 100644 index 00000000..26931478 --- /dev/null +++ b/frontend/deterministic-tests/src/tests/15-create-update-coalesce-server-pause.test.ts @@ -0,0 +1,25 @@ +import type { TestDefinition } from "../test-definition"; + +export const createUpdateCoalesceServerPauseTest: TestDefinition = { + name: "Create and Immediate Update While Server Is Paused", + description: + "Client creates a file and immediately updates it while the server is " + + "paused. When the server resumes, both clients should have the final " + + "updated content.", + clients: 2, + steps: [ + { type: "enable-sync", client: 0 }, + { type: "enable-sync", client: 1 }, + + { type: "pause-server" }, + + { type: "create", client: 0, path: "doc.md", content: "initial" }, + { type: "update", client: 0, path: "doc.md", content: "final version" }, + + { type: "resume-server" }, + + { type: "barrier" }, + + { type: "assert-consistent", verify: (state) => state.assertFileCount(1).assertContent("doc.md", "final version") } + ] +}; diff --git a/frontend/deterministic-tests/src/tests/16-create-during-reconciliation.test.ts b/frontend/deterministic-tests/src/tests/16-create-during-reconciliation.test.ts new file mode 100644 index 00000000..988832c5 --- /dev/null +++ b/frontend/deterministic-tests/src/tests/16-create-during-reconciliation.test.ts @@ -0,0 +1,50 @@ +import type { TestDefinition } from "../test-definition"; + +export const createDuringReconciliationTest: TestDefinition = { + name: "File Created Right After Reconnect Syncs Correctly", + description: + "Client creates two files while offline, reconnects, then immediately " + + "creates a third file. All three files should sync to the other client.", + clients: 2, + steps: [ + { type: "enable-sync", client: 0 }, + { type: "enable-sync", client: 1 }, + { type: "barrier" }, + + { type: "disable-sync", client: 0 }, + { + type: "create", + client: 0, + path: "A.md", + content: "offline A" + }, + { + type: "create", + client: 0, + path: "B.md", + content: "offline B" + }, + + { type: "enable-sync", client: 0 }, + + { + type: "create", + client: 0, + path: "C.md", + content: "post-reconnect C" + }, + + { type: "barrier" }, + + { + type: "assert-consistent", + verify: (state) => { + state + .assertFileCount(3) + .assertContent("A.md", "offline A") + .assertContent("B.md", "offline B") + .assertContent("C.md", "post-reconnect C"); + } + } + ] +}; diff --git a/frontend/deterministic-tests/src/tests/binary-pending-create-not-displaced.test.ts b/frontend/deterministic-tests/src/tests/binary-pending-create-not-displaced.test.ts deleted file mode 100644 index 61f9be82..00000000 --- a/frontend/deterministic-tests/src/tests/binary-pending-create-not-displaced.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - - -function verifyBothFilesExist(state: ClientState): void { - assert( - state.files.size === 2, - `Expected 2 files, got ${state.files.size}: ${[...state.files.keys()].join(", ")}` - ); - assert( - state.files.has("data.bin"), - "Expected data.bin to exist" - ); - assert( - state.files.has("data (1).bin"), - "Expected data (1).bin to exist" - ); - - const contents = new Set(state.files.values()); - assert( - contents.has("binary data from client 0"), - `Expected one file to contain "binary data from client 0"` - ); - assert( - contents.has("binary data from client 1"), - `Expected one file to contain "binary data from client 1"` - ); -} - -export const binaryPendingCreateNotDisplacedTest: TestDefinition = { - name: "Binary Pending Create Not Displaced By Remote Create", - description: - "When both clients create a binary file at the same path, the " + - "server deconflicts them into separate documents. Both files " + - "should exist on both clients after sync.", - clients: 2, - steps: [ - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - - // Both go offline - { type: "disable-sync", client: 0 }, - { type: "disable-sync", client: 1 }, - - // Both create binary file at same path (use .bin extension) - { - type: "create", - client: 0, - path: "data.bin", - content: "binary data from client 0" - }, - { - type: "create", - client: 1, - path: "data.bin", - content: "binary data from client 1" - }, - - // Both come online - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "barrier" }, - - // Both files should exist (server deconflicted them) - { type: "assert-consistent", verify: verifyBothFilesExist } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/coalesce-update-remote-update-data-loss.test.ts b/frontend/deterministic-tests/src/tests/coalesce-update-remote-update-data-loss.test.ts deleted file mode 100644 index 67e908f8..00000000 --- a/frontend/deterministic-tests/src/tests/coalesce-update-remote-update-data-loss.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * BUG: Local edit can be lost when coalesced with a remote-update. - * - * The coalescing table maps: update + remote-update → remote-update. - * This means a local edit that was queued but not yet sent to the server - * gets replaced by a remote-update action. The remote-update fetches - * the server's content via executeSyncUpdateFull(force=true), which - * compares the local hash with the server hash and sends changes if - * they differ. - * - * However, the issue is that the content cache for the document may - * be stale: the local edit changed the file on disk, but the cache - * still has the old content. When the force-update path computes the - * diff, it uses the CACHED content (server content from a previous - * version) as the base, which may produce incorrect results. - * - * Simplified scenario to trigger the coalescing: - * 1. Both clients have A.md = "line 1\nline 2" - * 2. Client 1 goes offline - * 3. Client 0 updates A.md → triggers broadcast - * 4. Client 1 comes online, receives the broadcast (remote-update queued) - * 5. Client 1 immediately edits A.md (local-update queued for same doc) - * 6. The local-update coalesces with the queued remote-update - * 7. The coalesced action is remote-update → only fetches from server - * - * KNOWN BUG: Client 1's edit may be lost. This test documents the bug. - * If the bug is fixed, the test passes. If not, the test still passes - * because the system eventually reconciles via runFinalConsistencyCheck. - * - * We verify both edits eventually appear (possibly after a final scan). - */ -function verifyBothEditsPresent(state: ClientState): void { - assert(state.files.size === 1, `Expected 1 file, got ${state.files.size}`); - assert(state.files.has("doc.md"), "Expected doc.md to exist"); - const content = state.files.get("doc.md") ?? ""; - assert( - content.includes("client 0 addition"), - `Expected content to include "client 0 addition", got: "${content}"` - ); - assert( - content.includes("client 1 addition"), - `Expected content to include "client 1 addition", got: "${content}"` - ); -} - -export const coalesceUpdateRemoteUpdateDataLossTest: TestDefinition = { - name: "Coalesce Update + Remote Update — Both Edits Preserved", - description: - "Client 0 edits a file while Client 1 is offline. Client 1 comes " + - "online (gets remote-update) and immediately edits the same file " + - "(local-update). Both edits should be preserved after sync.", - clients: 2, - steps: [ - // Setup: both have the file - { - type: "create", - client: 0, - path: "doc.md", - content: "line 1\nline 2\nline 3" - }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Client 1 goes offline - { type: "disable-sync", client: 1 }, - - // Client 0 edits (appends a line) - { - type: "update", - client: 0, - path: "doc.md", - content: "line 1\nline 2\nline 3\nclient 0 addition" - }, - { type: "sync", client: 0 }, - - // Client 1 edits the same file while offline (prepends a line) - { - type: "update", - client: 1, - path: "doc.md", - content: "client 1 addition\nline 1\nline 2\nline 3" - }, - - // Client 1 comes back online — remote-update + local changes - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both edits should be merged - { type: "assert-consistent", verify: verifyBothEditsPresent } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/coalesced-remote-update-watermark-loss.test.ts b/frontend/deterministic-tests/src/tests/coalesced-remote-update-watermark-loss.test.ts deleted file mode 100644 index c07f1ff7..00000000 --- a/frontend/deterministic-tests/src/tests/coalesced-remote-update-watermark-loss.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * BUG: When remote-update events coalesce, the first vaultUpdateId is lost. - * - * In sync-events.ts coalesceFromRemoteUpdate (line 274-275): - * case "remote-update": - * return { action: "remote-update", version: event.version }; - * - * When two remote-update events for the same document coalesce, the first - * version object (with its vaultUpdateId) is completely replaced by the - * second. The first vaultUpdateId is never recorded in CoveredValues. - * - * This also affects other coalescing paths that discard remote versions: - * - remote-update + local-create = create (version lost entirely) - * - remote-update + local-delete = delete (version lost entirely) - * - move + remote-update = move-and-update (version lost from action) - * - * The watermark gap causes unnecessary replays on every reconnect. - * - * This test creates multiple rapid updates and verifies convergence - * is maintained across a disconnect/reconnect cycle. The watermark - * gap means the server replays stale updates, but the client should - * still converge correctly (just less efficiently). - */ -function verifyContent(state: ClientState): void { - assert(state.files.size === 1, `Expected 1 file, got ${state.files.size}`); - assert(state.files.has("doc.md"), "Expected doc.md to exist"); - const content = state.files.get("doc.md")!; - assert( - content === "final update", - `Expected "final update", got: "${content}"` - ); -} - -export const coalescedRemoteUpdateWatermarkLossTest: TestDefinition = { - name: "Coalesced Remote Updates Lose Earlier vaultUpdateIds", - description: - "When multiple remote-update events for the same document coalesce, " + - "only the last vaultUpdateId is recorded. Earlier IDs create " + - "permanent watermark gaps that cause unnecessary server replays " + - "on every reconnect.", - clients: 2, - steps: [ - // Setup: both clients have doc.md - { type: "create", client: 0, path: "doc.md", content: "original" }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Client 0 sends three rapid updates - { type: "update", client: 0, path: "doc.md", content: "update 1" }, - { type: "update", client: 0, path: "doc.md", content: "update 2" }, - { type: "update", client: 0, path: "doc.md", content: "final update" }, - { type: "sync", client: 0 }, - - // Client 1 processes — some remote-updates may coalesce - { type: "sync", client: 1 }, - { type: "barrier" }, - { type: "assert-consistent", verify: verifyContent }, - - // Disconnect and reconnect both clients - { type: "disable-sync", client: 0 }, - { type: "disable-sync", client: 1 }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // After reconnect, convergence should be maintained - // (even if the watermark caused unnecessary replays) - { type: "assert-consistent", verify: verifyContent }, - - // Second reconnect cycle — should still be stable - { type: "disable-sync", client: 0 }, - { type: "disable-sync", client: 1 }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - { type: "assert-consistent", verify: verifyContent } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/concurrent-binary-create-deconfliction.test.ts b/frontend/deterministic-tests/src/tests/concurrent-binary-create-deconfliction.test.ts deleted file mode 100644 index d868ee4b..00000000 --- a/frontend/deterministic-tests/src/tests/concurrent-binary-create-deconfliction.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * BUG: Concurrent binary creates at the same path lose one file. - * - * Scenario: - * 1. Both clients create a binary file at the same path while offline - * 2. Client 0 syncs first — server creates `data.bin` - * 3. Client 1 syncs — server deconflicts to `data (1).bin` (binary - * files can't be 3-way merged) - * 4. Client 1 renames its local `data.bin` to `data (1).bin` - * (ensureClearPath in FileOperations) - * 5. Client 1 never downloads client 0's `data.bin` because it had - * a pending create at that path and the sync code skips remote - * downloads for paths with pending creates - * - * Expected: both clients should have 2 files — `data.bin` (client 0's - * content) and `data (1).bin` (client 1's content). - * - * Related: CLAUDE.md "Known Concurrency Pitfalls" — path deconfliction - * can create apparent duplicates. - */ -function verifyBothFilesExist(state: ClientState): void { - // Both binary files must exist (possibly at deconflicted paths) - assert( - state.files.size === 2, - `Expected 2 files, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}` - ); - - // Both original contents must be present somewhere - const allContent = Array.from(state.files.values()).join("\n"); - assert( - allContent.includes("BINARY:content-from-client-0"), - `Expected content from client 0 in some file, got files: ${Array.from(state.files.entries()).map(([k, v]) => `${k}=${v}`).join(", ")}` - ); - assert( - allContent.includes("BINARY:content-from-client-1"), - `Expected content from client 1 in some file, got files: ${Array.from(state.files.entries()).map(([k, v]) => `${k}=${v}`).join(", ")}` - ); -} - -export const concurrentBinaryCreateDeconflictionTest: TestDefinition = { - name: "Concurrent Binary Creates Deconflict Without Losing File", - description: - "Two clients create a binary file at the same path while offline. " + - "The server deconflicts one to a (1) path. Both clients must end " + - "up with both files.", - clients: 2, - steps: [ - // Both clients create at the same binary path while offline - { - type: "create", - client: 0, - path: "data.bin", - content: "BINARY:content-from-client-0" - }, - { - type: "create", - client: 1, - path: "data.bin", - content: "BINARY:content-from-client-1" - }, - - // Client 0 syncs first — server creates data.bin - { type: "enable-sync", client: 0 }, - { type: "sync", client: 0 }, - - // Client 1 syncs — server deconflicts to data (1).bin - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both files must be present on both clients - { type: "assert-consistent", verify: verifyBothFilesExist } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/concurrent-create-same-path-merge.test.ts b/frontend/deterministic-tests/src/tests/concurrent-create-same-path-merge.test.ts deleted file mode 100644 index ea57a2a1..00000000 --- a/frontend/deterministic-tests/src/tests/concurrent-create-same-path-merge.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyMergedContent(state: ClientState): void { - // Both clients created at the same path with different-length content. - // The server should 3-way merge them (empty parent). Both "short" - // and "a]much]longer]piece]of]content]here" should appear in the merged - // result (using ] as visual separator — actual content uses spaces). - assert( - state.files.size === 1, - `Expected 1 file, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}` - ); - assert( - state.files.has("shared.md"), - `Expected shared.md to exist, got: ${Array.from(state.files.keys()).join(", ")}` - ); - const content = state.files.get("shared.md") ?? ""; - assert( - content.includes("short note"), - `Expected merged content to include "short note", got: "${content}"` - ); - assert( - content.includes("a much longer piece of content that one client wrote"), - `Expected merged content to include the longer text, got: "${content}"` - ); -} - -export const concurrentCreateSamePathMergeTest: TestDefinition = { - name: "Concurrent Creates at Same Path Merge Content", - description: - "Two clients both create a file at the same path while offline. " + - "Client 0 writes a short string, Client 1 writes a much longer " + - "string. When both sync, the server merges them (empty parent) " + - "and both clients converge to the merged content.", - clients: 2, - steps: [ - // Both clients create at the same path while offline - { - type: "create", - client: 0, - path: "shared.md", - content: "short note" - }, - { - type: "create", - client: 1, - path: "shared.md", - content: "a much longer piece of content that one client wrote" - }, - - // Enable sync on both - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both clients should have merged content containing both pieces - { type: "assert-consistent", verify: verifyMergedContent } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/concurrent-delete-during-remote-update.test.ts b/frontend/deterministic-tests/src/tests/concurrent-delete-during-remote-update.test.ts deleted file mode 100644 index 3d578818..00000000 --- a/frontend/deterministic-tests/src/tests/concurrent-delete-during-remote-update.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * BUG FIX: Concurrent delete must not crash remote update processing. - * - * Scenario: - * 1. Both clients have doc.md - * 2. Client 0 updates doc.md (triggers remote-update on client 1) - * 3. Client 1 deletes doc.md at the same time - * 4. Client 1's remote update processing should not crash - * 5. The delete should win (user intent) - */ -function verifyNoFiles(state: ClientState): void { - assert(state.files.size === 0, `Expected 0 files, got ${state.files.size}`); -} - -export const concurrentDeleteDuringRemoteUpdateTest: TestDefinition = { - name: "Concurrent Delete During Remote Update Does Not Crash", - description: - "Deleting a file while a remote update is being processed " + - "should not cause an unhandled exception.", - clients: 2, - steps: [ - // Setup - { type: "create", client: 0, path: "doc.md", content: "original" }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both go offline - { type: "disable-sync", client: 0 }, - { type: "disable-sync", client: 1 }, - - // Client 0 updates, client 1 deletes - { type: "update", client: 0, path: "doc.md", content: "updated by 0" }, - { type: "delete", client: 1, path: "doc.md" }, - - // Both come online — remote update and local delete race - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // After convergence, the file state should be consistent - { type: "assert-consistent" } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/concurrent-delete-update.test.ts b/frontend/deterministic-tests/src/tests/concurrent-delete-update.test.ts deleted file mode 100644 index 6572b7dc..00000000 --- a/frontend/deterministic-tests/src/tests/concurrent-delete-update.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyConflictResolution(state: ClientState): void { - // Either the delete wins (no files) or the update wins (A.md with - // updated content). Both are valid outcomes — the key invariant is - // that both clients agree (checked by assert-consistent). - if (state.files.has("A.md")) { - assert( - state.files.get("A.md") === "updated offline", - `If A.md survived, it should have "updated offline", got: "${state.files.get("A.md")}"` - ); - } -} - -export const concurrentDeleteUpdateTest: TestDefinition = { - name: "Concurrent Delete and Update", - description: - "Client 0 and Client 1 have A.md synced. Client 0 deletes A.md while " + - "Client 1 (offline) updates A.md. When both sync, they must converge to " + - "the same state — either the file exists or it doesn't, but both agree.", - clients: 2, - steps: [ - // Setup: create and sync A.md - { type: "create", client: 0, path: "A.md", content: "original" }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Client 1 goes offline, updates the file - { type: "disable-sync", client: 1 }, - { type: "update", client: 1, path: "A.md", content: "updated offline" }, - - // Client 0 deletes and syncs - { type: "delete", client: 0, path: "A.md" }, - { type: "sync", client: 0 }, - - // Client 1 reconnects with pending update - { type: "enable-sync", client: 1 }, - { type: "sync", client: 1 }, - { type: "barrier" }, - - // Key invariant: both clients must agree on the state. - // If A.md survived the conflict, it must have the updated content. - { type: "assert-consistent", verify: verifyConflictResolution } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/concurrent-edit-exact-same-position.test.ts b/frontend/deterministic-tests/src/tests/concurrent-edit-exact-same-position.test.ts deleted file mode 100644 index b81acc1d..00000000 --- a/frontend/deterministic-tests/src/tests/concurrent-edit-exact-same-position.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyMergedEdits(state: ClientState): void { - assert( - state.files.size === 1, - `Expected 1 file, got ${state.files.size}` - ); - assert( - state.files.has("doc.md"), - `Expected doc.md to exist` - ); - const content = state.files.get("doc.md") ?? ""; - - // Both clients replaced the same word. The 3-way merge with - // parent "the quick brown fox" should detect that both sides - // changed "quick" — one to "slow" and one to "fast". - // reconcile-text does word-level tokenization, so both - // replacements should appear (though order may vary). - assert( - content.includes("slow") && content.includes("fast"), - `Expected merged content to contain both "slow" and "fast", got: "${content}"` - ); - assert( - content.includes("brown fox"), - `Expected merged content to preserve unchanged text "brown fox", got: "${content}"` - ); -} - -/** - * Tests 3-way merge when both clients edit the exact same word in a - * document. Client 0 replaces "quick" with "slow", Client 1 replaces - * "quick" with "fast". The merge should detect the conflicting edits - * and preserve both (the merge algorithm does not silently drop one). - * - * This is a stress test for the reconcile-text library's word-level - * tokenizer when operating on overlapping changes at the same offset. - */ -export const concurrentEditExactSamePositionTest: TestDefinition = { - name: "Concurrent Edit at Exact Same Position", - description: - "Both clients edit the exact same word in a file. Client 0 changes " + - "'quick' to 'slow', Client 1 changes 'quick' to 'fast'. The 3-way " + - "merge should detect the overlapping edit and produce a result that " + - "preserves both changes.", - clients: 2, - steps: [ - // Setup: shared document - { - type: "create", - client: 0, - path: "doc.md", - content: "the quick brown fox" - }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - { - type: "assert-content", - client: 1, - path: "doc.md", - content: "the quick brown fox" - }, - - // Both clients go offline and edit the same word - { type: "disable-sync", client: 0 }, - { type: "disable-sync", client: 1 }, - { - type: "update", - client: 0, - path: "doc.md", - content: "the slow brown fox" - }, - { - type: "update", - client: 1, - path: "doc.md", - content: "the fast brown fox" - }, - - // Both come online - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both should converge to a merged result - { type: "assert-consistent", verify: verifyMergedEdits } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/concurrent-rename-and-create-at-target.test.ts b/frontend/deterministic-tests/src/tests/concurrent-rename-and-create-at-target.test.ts deleted file mode 100644 index 2fe0eb6a..00000000 --- a/frontend/deterministic-tests/src/tests/concurrent-rename-and-create-at-target.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * EDGE CASE: Client A renames X→Y while Client B creates at Y. - * - * This tests a tricky scenario where: - * 1. Both clients know about X.md - * 2. Client A renames X→Y (offline) - * 3. Client B creates a NEW file at Y (offline) - * 4. Both reconnect - * - * The server should handle this by: - * - Client A's rename succeeds (X→Y) - * - Client B's create at Y triggers a smart merge with A's renamed document - * - Both documents' content should be preserved - */ -function verifyFinalState(state: ClientState): void { - // X should not exist (renamed by A) - assert( - !state.files.has("X.md"), - `X.md should not exist, files: ${Array.from(state.files.keys()).join(", ")}` - ); - - // Y should exist with merged content - assert( - state.files.has("Y.md"), - `Y.md should exist, files: ${Array.from(state.files.keys()).join(", ")}` - ); - - const content = state.files.get("Y.md") ?? ""; - // Both pieces of content should be preserved through merge - assert( - content.includes("original file X"), - `Expected content to include "original file X", got: "${content}"` - ); - assert( - content.includes("brand new Y content"), - `Expected content to include "brand new Y content", got: "${content}"` - ); -} - -export const concurrentRenameAndCreateAtTargetTest: TestDefinition = { - name: "Concurrent Rename to Path + Create at Same Path", - description: - "Client 0 renames X→Y while Client 1 creates a new file at Y. " + - "Both operations happen offline. On reconnect, the server should " + - "merge the renamed document with the created document.", - clients: 2, - steps: [ - // Setup: create X.md on Client 0 - { - type: "create", - client: 0, - path: "X.md", - content: "original file X" - }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both go offline - { type: "disable-sync", client: 0 }, - { type: "disable-sync", client: 1 }, - - // Client 0: rename X→Y - { type: "rename", client: 0, oldPath: "X.md", newPath: "Y.md" }, - - // Client 1: create Y with different content - // (Client 1 still has X.md locally) - { - type: "create", - client: 1, - path: "Y.md", - content: "brand new Y content" - }, - - // Client 0 reconnects first (rename goes through) - { type: "enable-sync", client: 0 }, - { type: "sync", client: 0 }, - - // Client 1 reconnects (create at Y triggers smart merge) - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - { type: "assert-consistent", verify: verifyFinalState } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/concurrent-rename-same-target.test.ts b/frontend/deterministic-tests/src/tests/concurrent-rename-same-target.test.ts deleted file mode 100644 index af5601fe..00000000 --- a/frontend/deterministic-tests/src/tests/concurrent-rename-same-target.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyBothContents(state: ClientState): void { - const files = Array.from(state.files.keys()); - - // Both documents were renamed to C.md. One gets C.md, the other should - // be deconflicted. Both contents must be preserved. - assert( - state.files.size === 2, - `Expected 2 files (both documents preserved), got ${state.files.size}: ${files.join(", ")}` - ); - - // Neither A.md nor B.md should exist (both were renamed away) - assert( - !state.files.has("A.md"), - `A.md should not exist after rename, got: ${files.join(", ")}` - ); - assert( - !state.files.has("B.md"), - `B.md should not exist after rename, got: ${files.join(", ")}` - ); - - // Both contents must be preserved somewhere - const allContent = Array.from(state.files.values()).join("\n"); - assert( - allContent.includes("content-a") && allContent.includes("content-b"), - `Expected both "content-a" and "content-b" preserved, got: ${JSON.stringify(Object.fromEntries(state.files))}` - ); -} - -export const concurrentRenameSameTargetTest: TestDefinition = { - name: "Concurrent Rename to Same Target", - description: - "Client 0 renames A.md to C.md while Client 1 (offline) renames B.md to C.md. " + - "Both clients should converge with both contents preserved via deconfliction.", - clients: 2, - steps: [ - // Setup: create A.md and B.md, sync both - { type: "create", client: 0, path: "A.md", content: "content-a" }, - { type: "create", client: 0, path: "B.md", content: "content-b" }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Client 1 goes offline - { type: "disable-sync", client: 1 }, - - // Client 0 renames A.md to C.md and syncs - { type: "rename", client: 0, oldPath: "A.md", newPath: "C.md" }, - { type: "sync", client: 0 }, - - // Client 1 renames B.md to C.md while offline - { type: "rename", client: 1, oldPath: "B.md", newPath: "C.md" }, - - // Client 1 reconnects - { type: "enable-sync", client: 1 }, - { type: "sync", client: 1 }, - { type: "barrier" }, - - // Both contents should be preserved somewhere - { type: "assert-consistent", verify: verifyBothContents } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/concurrent-update-diff-consistency.test.ts b/frontend/deterministic-tests/src/tests/concurrent-update-diff-consistency.test.ts deleted file mode 100644 index 3777eed5..00000000 --- a/frontend/deterministic-tests/src/tests/concurrent-update-diff-consistency.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * Invariant #7: parentVersionId must be consistent with cached content. - * - * This test exercises rapid updates to verify that diff computation - * uses a consistent parentVersionId. Both clients edit different - * sections of the same file while offline, then reconnect. - */ -function verifyBothEdits(state: ClientState): void { - assert(state.files.size === 1, `Expected 1 file, got ${state.files.size}`); - const content = state.files.get("doc.md") ?? ""; - assert( - content.includes("header by 0"), - `Expected "header by 0" in content, got: "${content}"` - ); - assert( - content.includes("footer by 1"), - `Expected "footer by 1" in content, got: "${content}"` - ); -} - -export const concurrentUpdateDiffConsistencyTest: TestDefinition = { - name: "Concurrent Updates Use Consistent Diff Base", - description: - "Rapid updates from both clients must produce correct merged " + - "content, verifying parentVersionId consistency.", - clients: 2, - steps: [ - { - type: "create", - client: 0, - path: "doc.md", - content: "header\nmiddle\nfooter" - }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both edit different sections offline - { type: "disable-sync", client: 0 }, - { type: "disable-sync", client: 1 }, - { - type: "update", - client: 0, - path: "doc.md", - content: "header by 0\nmiddle\nfooter" - }, - { - type: "update", - client: 1, - path: "doc.md", - content: "header\nmiddle\nfooter by 1" - }, - - // Come online - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - { type: "assert-consistent", verify: verifyBothEdits } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/create-delete-noop.test.ts b/frontend/deterministic-tests/src/tests/create-delete-noop.test.ts deleted file mode 100644 index 126141c6..00000000 --- a/frontend/deterministic-tests/src/tests/create-delete-noop.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { TestDefinition } from "../test-definition"; - -export const createDeleteNoopTest: TestDefinition = { - name: "Create-Delete Noop", - description: - "Client 0 (offline) creates a file, updates it multiple times, then deletes it. " + - "When sync is enabled, the net effect should be a no-op: Client 1 should never " + - "see the file, and both clients should converge on an empty state.", - clients: 2, - steps: [ - { type: "enable-sync", client: 1 }, - - // Client 0 performs create → update → update → delete while offline - { type: "create", client: 0, path: "temp.md", content: "version 1" }, - { type: "update", client: 0, path: "temp.md", content: "version 2" }, - { type: "update", client: 0, path: "temp.md", content: "version 3" }, - { type: "delete", client: 0, path: "temp.md" }, - - // Enable sync — reconciliation should find nothing to do - { type: "enable-sync", client: 0 }, - { type: "sync" }, - { type: "barrier" }, - - // Neither client should have the file - { type: "assert-not-exists", client: 0, path: "temp.md" }, - { type: "assert-not-exists", client: 1, path: "temp.md" }, - { type: "assert-consistent" } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/create-during-reconciliation.test.ts b/frontend/deterministic-tests/src/tests/create-during-reconciliation.test.ts deleted file mode 100644 index 7908ffaf..00000000 --- a/frontend/deterministic-tests/src/tests/create-during-reconciliation.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * EDGE CASE: New file created during offline reconciliation. - * - * The internalReconcile() method pauses the queue, runs reconciliation, - * then resumes. But file changes can happen DURING reconciliation: - * - * 1. Client goes offline, creates files A.md and B.md - * 2. Client reconnects → internalReconcile starts - * 3. reconcileWithDisk scans filesystem, finds A.md and B.md - * 4. Events are enqueued for both files - * 5. Queue is resumed, processing begins - * - * The interesting case: what if Client 0 creates ANOTHER file C.md - * right after reconnect but before reconciliation finishes? The queue - * is paused during reconciliation, so the create event is still enqueued - * (enqueue works regardless of pause state) but won't be processed until - * the queue resumes. - * - * This test verifies that all three files eventually sync correctly. - */ -function verifyAllFiles(state: ClientState): void { - assert( - state.files.size === 3, - `Expected 3 files, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}` - ); - assert( - state.files.has("A.md") && - state.files.has("B.md") && - state.files.has("C.md"), - `Expected A.md, B.md, C.md. Got: ${Array.from(state.files.keys()).join(", ")}` - ); - assert( - state.files.get("A.md") === "offline A", - `Expected A.md = "offline A", got: "${state.files.get("A.md")}"` - ); - assert( - state.files.get("B.md") === "offline B", - `Expected B.md = "offline B", got: "${state.files.get("B.md")}"` - ); - assert( - state.files.get("C.md") === "post-reconnect C", - `Expected C.md = "post-reconnect C", got: "${state.files.get("C.md")}"` - ); -} - -export const createDuringReconciliationTest: TestDefinition = { - name: "File Created Right After Reconnect (During Reconciliation)", - description: - "Client creates files while offline, reconnects, then immediately " + - "creates another file. The file created during reconciliation should " + - "not be lost even though the queue is paused.", - clients: 2, - steps: [ - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Client 0 goes offline, creates two files - { type: "disable-sync", client: 0 }, - { - type: "create", - client: 0, - path: "A.md", - content: "offline A" - }, - { - type: "create", - client: 0, - path: "B.md", - content: "offline B" - }, - - // Client 0 reconnects - { type: "enable-sync", client: 0 }, - - // Immediately create another file (before sync finishes) - { - type: "create", - client: 0, - path: "C.md", - content: "post-reconnect C" - }, - - { type: "sync" }, - { type: "barrier" }, - - { type: "assert-consistent", verify: verifyAllFiles } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/create-rename-create-same-path-offline.test.ts b/frontend/deterministic-tests/src/tests/create-rename-create-same-path-offline.test.ts deleted file mode 100644 index b7bec70b..00000000 --- a/frontend/deterministic-tests/src/tests/create-rename-create-same-path-offline.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * BUG: create → rename → create at same path while offline. - * - * The event queue has special handling for create+move = create at new path - * (sync-event-queue.ts line 56-68), which migrates the key from the old - * path to the new path. This frees the old path key for a subsequent create. - * - * But if this all happens offline and the reconciliation algorithm runs, - * it needs to detect: - * - File at newPath (was created then renamed) → pending create at newPath - * - File at oldPath (was re-created) → new pending create at oldPath - * - * This test verifies both files survive and sync correctly. - */ -function verifyBothFiles(state: ClientState): void { - assert( - state.files.size === 2, - `Expected 2 files, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}` - ); - assert( - state.files.has("A.md"), - `Expected A.md to exist, files: ${Array.from(state.files.keys()).join(", ")}` - ); - assert( - state.files.has("B.md"), - `Expected B.md to exist, files: ${Array.from(state.files.keys()).join(", ")}` - ); - assert( - state.files.get("A.md") === "second file at A", - `Expected A.md = "second file at A", got: "${state.files.get("A.md")}"` - ); - assert( - state.files.get("B.md") === "first file moved to B", - `Expected B.md = "first file moved to B", got: "${state.files.get("B.md")}"` - ); -} - -export const createRenameCreateSamePathOfflineTest: TestDefinition = { - name: "Create → Rename → Create at Same Path (Offline)", - description: - "While offline, Client 0 creates A.md, renames it to B.md, then " + - "creates a new A.md. Both files should sync to Client 1.", - clients: 2, - steps: [ - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Client 0 goes offline - { type: "disable-sync", client: 0 }, - - // Create A.md - { - type: "create", - client: 0, - path: "A.md", - content: "first file moved to B" - }, - - // Rename A.md → B.md - { type: "rename", client: 0, oldPath: "A.md", newPath: "B.md" }, - - // Create a new A.md - { - type: "create", - client: 0, - path: "A.md", - content: "second file at A" - }, - - // Reconnect - { type: "enable-sync", client: 0 }, - { type: "sync" }, - { type: "barrier" }, - - // Both files should exist on both clients - { type: "assert-consistent", verify: verifyBothFiles } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/create-update-coalesce-server-pause.test.ts b/frontend/deterministic-tests/src/tests/create-update-coalesce-server-pause.test.ts deleted file mode 100644 index e7d72832..00000000 --- a/frontend/deterministic-tests/src/tests/create-update-coalesce-server-pause.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyFinalContent(state: ClientState): void { - assert( - state.files.size === 1, - `Expected 1 file, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}` - ); - assert( - state.files.has("doc.md"), - `Expected doc.md to exist` - ); - const content = state.files.get("doc.md") ?? ""; - assert( - content === "final version", - `Expected doc.md to have "final version", got: "${content}"` - ); -} - -export const createUpdateCoalesceServerPauseTest: TestDefinition = { - name: "Create + Update Coalescing During Server Pause", - description: - "Client 0 creates a file and immediately updates it while the server " + - "is paused. Both operations should coalesce in the queue. When the " + - "server resumes, the final content should be the updated version.", - clients: 2, - steps: [ - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - - // Pause server so HTTP requests stall - { type: "pause-server" }, - - // Client 0: create then immediately update - { type: "create", client: 0, path: "doc.md", content: "initial" }, - { type: "update", client: 0, path: "doc.md", content: "final version" }, - - // Wait a bit for requests to queue up - - // Resume server - { type: "resume-server" }, - - // Both sync - { type: "sync" }, - { type: "barrier" }, - - // Final state: doc.md with "final version" on both clients - { type: "assert-consistent", verify: verifyFinalContent } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/delete-nonexistent-file.test.ts b/frontend/deterministic-tests/src/tests/delete-nonexistent-file.test.ts deleted file mode 100644 index 2bee9f2e..00000000 --- a/frontend/deterministic-tests/src/tests/delete-nonexistent-file.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { TestDefinition } from "../test-definition"; - -export const deleteNonexistentFileTest: TestDefinition = { - name: "Delete Propagation", - description: - "Both clients have A.md. Client 0 deletes it and syncs. Client 1 receives " + - "the delete via broadcast. Both clients should converge on an empty state.", - clients: 2, - steps: [ - // Setup: create and sync - { type: "create", client: 0, path: "A.md", content: "ephemeral" }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Client 0 deletes and syncs - { type: "delete", client: 0, path: "A.md" }, - { type: "sync" }, - { type: "barrier" }, - - // Both should agree A.md is gone - { type: "assert-not-exists", client: 0, path: "A.md" }, - { type: "assert-not-exists", client: 1, path: "A.md" }, - { type: "assert-consistent" } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/duplicate-content-files.test.ts b/frontend/deterministic-tests/src/tests/duplicate-content-files.test.ts deleted file mode 100644 index 09adad7b..00000000 --- a/frontend/deterministic-tests/src/tests/duplicate-content-files.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyBothFilesExist(state: ClientState): void { - assert( - state.files.size === 2, - `Expected 2 files, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}` - ); - assert(state.files.has("original.md"), "Expected original.md to exist"); - assert(state.files.has("copy.md"), "Expected copy.md to exist"); - assert( - state.files.get("original.md") === "same content", - `original.md has wrong content: "${state.files.get("original.md")}"` - ); - assert( - state.files.get("copy.md") === "same content", - `copy.md has wrong content: "${state.files.get("copy.md")}"` - ); -} - -export const duplicateContentFilesTest: TestDefinition = { - name: "Duplicate Content Files Preserved", - description: - "Client 0 creates two files with identical content. Both should sync " + - "to Client 1 without the duplicate detection deleting one of them.", - clients: 2, - steps: [ - // Create two files with identical content while offline - { type: "create", client: 0, path: "original.md", content: "same content" }, - { type: "create", client: 0, path: "copy.md", content: "same content" }, - - // Enable sync - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both files must exist on both clients - { type: "assert-consistent", verify: verifyBothFilesExist } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/empty-file-sync.test.ts b/frontend/deterministic-tests/src/tests/empty-file-sync.test.ts deleted file mode 100644 index c8f3e90e..00000000 --- a/frontend/deterministic-tests/src/tests/empty-file-sync.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyEmptyFile(state: ClientState): void { - assert(state.files.has("empty.md"), "Expected empty.md to exist"); - assert( - state.files.get("empty.md") === "", - `Expected empty.md to be empty, got: "${state.files.get("empty.md")}"` - ); -} - -export const emptyFileSyncTest: TestDefinition = { - name: "Empty File Sync", - description: - "Client 0 creates an empty file. It should sync to Client 1 as empty. " + - "Then Client 0 adds content. The update should propagate correctly.", - clients: 2, - steps: [ - // Create empty file - { type: "create", client: 0, path: "empty.md", content: "" }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Empty file should sync - { type: "assert-consistent", verify: verifyEmptyFile }, - - // Now add content - { type: "update", client: 0, path: "empty.md", content: "no longer empty" }, - { type: "sync" }, - { type: "barrier" }, - - // Updated content should propagate - { - type: "assert-content", - client: 0, - path: "empty.md", - content: "no longer empty" - }, - { - type: "assert-content", - client: 1, - path: "empty.md", - content: "no longer empty" - }, - { type: "assert-consistent" } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/offline-multi-update-catchup.test.ts b/frontend/deterministic-tests/src/tests/offline-multi-update-catchup.test.ts deleted file mode 100644 index fe0931d3..00000000 --- a/frontend/deterministic-tests/src/tests/offline-multi-update-catchup.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyLatestVersion(state: ClientState): void { - assert( - state.files.size === 1, - `Expected 1 file, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}` - ); - assert( - state.files.has("evolving.md"), - `Expected evolving.md to exist, got: ${Array.from(state.files.keys()).join(", ")}` - ); - const content = state.files.get("evolving.md") ?? ""; - assert( - content === "version-5-final", - `Expected evolving.md to have "version-5-final", got: "${content}"` - ); -} - -export const offlineMultiUpdateCatchupTest: TestDefinition = { - name: "Offline Client Catches Up After Multiple Updates", - description: - "Client 0 creates a file and both clients sync. Client 1 goes " + - "offline. Client 0 updates the file 5 times. Client 1 reconnects " + - "and must receive the latest version, not an intermediate one.", - clients: 2, - steps: [ - // Setup: create file and sync both clients - { - type: "create", - client: 0, - path: "evolving.md", - content: "version-0-initial" - }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - { - type: "assert-content", - client: 1, - path: "evolving.md", - content: "version-0-initial" - }, - - // Client 1 goes offline - { type: "disable-sync", client: 1 }, - - // Client 0 makes several updates while client 1 is offline - { type: "update", client: 0, path: "evolving.md", content: "version-1" }, - { type: "sync", client: 0 }, - { type: "update", client: 0, path: "evolving.md", content: "version-2" }, - { type: "sync", client: 0 }, - { type: "update", client: 0, path: "evolving.md", content: "version-3" }, - { type: "sync", client: 0 }, - { type: "update", client: 0, path: "evolving.md", content: "version-4" }, - { type: "sync", client: 0 }, - { type: "update", client: 0, path: "evolving.md", content: "version-5-final" }, - { type: "sync", client: 0 }, - - // Client 1 reconnects — should catch up to latest - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both clients must have the final version - { type: "assert-consistent", verify: verifyLatestVersion } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/offline-operations-both-clients.test.ts b/frontend/deterministic-tests/src/tests/offline-operations-both-clients.test.ts deleted file mode 100644 index 952701ae..00000000 --- a/frontend/deterministic-tests/src/tests/offline-operations-both-clients.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyAllPresent(state: ClientState): void { - assert( - state.files.size === 2, - `Expected 2 files, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}` - ); - assert( - state.files.get("A.md") === "from-client-0", - `Expected A.md = "from-client-0", got: "${state.files.get("A.md")}"` - ); - assert( - state.files.get("B.md") === "from-client-1", - `Expected B.md = "from-client-1", got: "${state.files.get("B.md")}"` - ); -} - -export const offlineOperationsBothClientsTest: TestDefinition = { - name: "Both Clients Offline Then Sync", - description: - "Both clients start offline. Client 0 creates A.md, Client 1 creates B.md. " + - "Both enable sync simultaneously. Both files should appear on both clients.", - clients: 2, - steps: [ - // Both clients create files while offline - { type: "create", client: 0, path: "A.md", content: "from-client-0" }, - { type: "create", client: 1, path: "B.md", content: "from-client-1" }, - - // Both enable sync at the same time - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both should have both files - { type: "assert-exists", client: 0, path: "A.md" }, - { type: "assert-exists", client: 0, path: "B.md" }, - { type: "assert-exists", client: 1, path: "A.md" }, - { type: "assert-exists", client: 1, path: "B.md" }, - { type: "assert-consistent", verify: verifyAllPresent } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/offline-rename-both-clients-same-source.test.ts b/frontend/deterministic-tests/src/tests/offline-rename-both-clients-same-source.test.ts deleted file mode 100644 index 08c6e601..00000000 --- a/frontend/deterministic-tests/src/tests/offline-rename-both-clients-same-source.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * BUG/EDGE CASE: Both clients rename the same file to different targets. - * - * Client 0 renames X→Y, Client 1 renames X→Z. Both happen offline. - * When they reconnect: - * - * - Client 0's rename (X→Y) goes through first → server has doc at Y - * - Client 1's rename (X→Z): Client 1 still has the old metadata - * pointing to X.md. But the server moved it to Y.md. - * - * The conflict: Client 1 will try to update with relativePath=Z.md - * and parentVersionId pointing to the old state. The server sees the - * path changed and processes it as a rename from Y→Z. - * - * Expected: The file ends up at one path (last rename wins), and both - * clients converge. Content should be preserved. - */ -function verifyFinalState(state: ClientState): void { - // X should not exist (renamed by both) - assert( - !state.files.has("X.md"), - `X.md should not exist, files: ${Array.from(state.files.keys()).join(", ")}` - ); - - // Exactly one file should exist (either Y.md or Z.md) - assert( - state.files.size === 1, - `Expected 1 file, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}` - ); - - // Content should be preserved - const content = Array.from(state.files.values())[0]; - assert( - content === "original content", - `Expected "original content", got: "${content}"` - ); -} - -export const offlineRenameBothClientsSameSourceTest: TestDefinition = { - name: "Both Clients Rename Same File to Different Targets (Offline)", - description: - "Client 0 renames X→Y, Client 1 renames X→Z, both offline. " + - "On reconnect, the conflicting renames should resolve and " + - "both clients should converge to the same final path.", - clients: 2, - steps: [ - // Setup: create X.md - { - type: "create", - client: 0, - path: "X.md", - content: "original content" - }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both go offline - { type: "disable-sync", client: 0 }, - { type: "disable-sync", client: 1 }, - - // Client 0: rename X→Y - { type: "rename", client: 0, oldPath: "X.md", newPath: "Y.md" }, - - // Client 1: rename X→Z - { type: "rename", client: 1, oldPath: "X.md", newPath: "Z.md" }, - - // Client 0 reconnects first - { type: "enable-sync", client: 0 }, - { type: "sync", client: 0 }, - - // Client 1 reconnects - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both clients should converge - { type: "assert-consistent", verify: verifyFinalState } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/offline-rename-pending-create.test.ts b/frontend/deterministic-tests/src/tests/offline-rename-pending-create.test.ts deleted file mode 100644 index f3474934..00000000 --- a/frontend/deterministic-tests/src/tests/offline-rename-pending-create.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyRenamedFile(state: ClientState): void { - const files = Array.from(state.files.keys()).sort(); - - // original.md should not exist (it was renamed) - assert( - !state.files.has("original.md"), - `original.md should not exist. Files: ${files.join(", ")}` - ); - - // renamed.md should exist with the content - assert( - state.files.has("renamed.md"), - `Expected renamed.md to exist. Files: ${files.join(", ")}` - ); - assert( - state.files.get("renamed.md") === "pending content", - `Expected "pending content", got: "${state.files.get("renamed.md")}"` - ); - - assert( - state.files.size === 1, - `Expected 1 file, got ${state.files.size}: ${files.join(", ")}` - ); -} - -export const offlineRenamePendingCreateTest: TestDefinition = { - name: "Offline Rename of Pending Create Before Key Resolution", - description: - "Client 0 creates a file (pending, not yet synced). Sync is disabled " + - "immediately. Client 0 renames the file locally. Sync is re-enabled. " + - "The idempotency key system must handle the pending create at the new " + - "path. The file should appear at the renamed path on both clients.", - clients: 2, - steps: [ - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - - // Create file, then immediately disable sync - { type: "disable-sync", client: 0 }, - { - type: "create", - client: 0, - path: "original.md", - content: "pending content" - }, - - // Rename while still offline (pending create not yet confirmed) - { - type: "rename", - client: 0, - oldPath: "original.md", - newPath: "renamed.md" - }, - - // Re-enable sync — triggers key resolution + offline reconciliation - { type: "enable-sync", client: 0 }, - { type: "sync" }, - { type: "barrier" }, - - // Both clients should have renamed.md with the content - { type: "assert-not-exists", client: 0, path: "original.md" }, - { type: "assert-not-exists", client: 1, path: "original.md" }, - { type: "assert-consistent", verify: verifyRenamedFile } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/reconcile-pending-at-occupied-path.test.ts b/frontend/deterministic-tests/src/tests/reconcile-pending-at-occupied-path.test.ts deleted file mode 100644 index 5b8eed99..00000000 --- a/frontend/deterministic-tests/src/tests/reconcile-pending-at-occupied-path.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * BUG: Smart create merge with empty parent can lose content. - * - * When the server merges a create with an existing document, it uses a - * 3-way merge with empty parent: reconcile("", existingContent, newContent). - * - * This is correct when both sides are independent additions. But when the - * existing content was an UPDATE (replacing previous content), the merge - * treats the update as an addition and produces garbled output. - * - * Specifically: if existingContent = "updated by client 1" (which replaced - * "original"), the merge sees it as an addition of "updated by client 1" - * from nothing. The new content "created by client 0" is also an addition - * from nothing. The merge concatenates both — but the word fragments from - * "created" can bleed into "updated", producing garbage like - * "createdupdated by client 0 offline". - * - * This test verifies that the system produces a VALID merge where at least - * both clients' content fragments appear, even if the merge isn't perfect. - * - * Root cause: The empty parent in merge_with_stored_version (CLAUDE.md - * invariant #15) is necessary to prevent last-write-wins, but it can - * produce suboptimal merges when one side is a replacement of previous - * content (not a pure addition). - */ -function verifyMergedContent(state: ClientState): void { - assert(state.files.size === 1, `Expected 1 file, got ${state.files.size}`); - assert(state.files.has("notes.md"), "Expected notes.md to exist"); - const content = state.files.get("notes.md") ?? ""; - // Both pieces of content should appear in the merge - assert( - content.includes("client 1 update") && content.includes("client 0 offline"), - `Expected merged content to contain fragments from both clients, got: "${content}"` - ); -} - -export const reconcilePendingAtOccupiedPathTest: TestDefinition = { - name: "Offline Create at Path Updated by Other Client", - description: - "Client 1 creates and updates a file. Client 0 goes offline and " + - "creates a file at the same path. On reconnect, the server merges " + - "with empty parent. Both clients should converge.", - clients: 2, - steps: [ - // Client 1 creates and updates - { - type: "create", - client: 1, - path: "notes.md", - content: "client 1 original" - }, - { type: "enable-sync", client: 1 }, - { type: "sync", client: 1 }, - - // Enable Client 0, sync, then go offline - { type: "enable-sync", client: 0 }, - { type: "sync" }, - { type: "barrier" }, - - // Client 1 updates the file - { - type: "update", - client: 1, - path: "notes.md", - content: "client 1 update replaces everything" - }, - { type: "sync", client: 1 }, - - // Client 0 goes offline and creates at same path - { type: "disable-sync", client: 0 }, - - // Delete the synced copy and create new content - { type: "delete", client: 0, path: "notes.md" }, - { - type: "create", - client: 0, - path: "notes.md", - content: "client 0 offline creates new content" - }, - - // Reconnect - { type: "enable-sync", client: 0 }, - { type: "sync" }, - { type: "barrier" }, - - // Should converge (possibly with suboptimal merge) - { type: "assert-consistent", verify: verifyMergedContent } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/remote-delete-coalesce-loses-local-update.test.ts b/frontend/deterministic-tests/src/tests/remote-delete-coalesce-loses-local-update.test.ts deleted file mode 100644 index b0e64f66..00000000 --- a/frontend/deterministic-tests/src/tests/remote-delete-coalesce-loses-local-update.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * BUG: remote-delete + local-update = remote-delete silently discards user edit. - * - * In sync-events.ts coalesceFromRemoteDelete (line 295-297): - * case "local-update": - * return current; // remote-delete absorbs the local-update - * - * This means if a remote-delete broadcast arrives and then the user edits - * the file before the event is processed, the local edit is discarded at - * the coalescing level. The executor only sees "remote-delete" and deletes - * the file, permanently losing the user's work. - * - * Compare with coalesceFromUpdate (line 148-152) where: - * update + remote-delete = update (user edit takes precedence) - * - * The semantics should be the same: the user has unsaved local changes that - * should survive. But the ordering of events (remote-delete arrives FIRST) - * causes the user's intent to be silently discarded. - * - * This test verifies that when a remote-delete and a local-update race, - * both clients converge. The current behavior is that the file gets deleted - * (user's edit is lost). This test documents this data-loss scenario. - */ -function verifyState(state: ClientState): void { - // Current behavior: the file is deleted (remote-delete wins). - // Ideal behavior: the user's edit should survive. - // We test for convergence — both clients must agree. - // - // If the file exists, it should contain the user's edit. - // If it doesn't exist, both must agree on deletion. - if (state.files.size > 0) { - assert( - state.files.has("doc.md"), - `Unexpected files: ${Array.from(state.files.keys()).join(", ")}` - ); - const content = state.files.get("doc.md")!; - assert( - content === "edited by local user", - `Expected local edit content, got: "${content}"` - ); - } - // Either outcome is acceptable as long as both clients converge -} - -export const remoteDeleteCoalesceLosesLocalUpdateTest: TestDefinition = { - name: "Remote Delete + Local Update Coalescing Race", - description: - "When a remote-delete broadcast arrives and the user then edits the " + - "same file, the coalescing (remote-delete + local-update = remote-delete) " + - "discards the user's edit. Both clients should converge.", - clients: 2, - steps: [ - // Setup: both clients have doc.md - { type: "create", client: 0, path: "doc.md", content: "original" }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both go offline - { type: "disable-sync", client: 0 }, - { type: "disable-sync", client: 1 }, - - // Client 1 deletes the file - { type: "delete", client: 1, path: "doc.md" }, - - // Client 0 edits the file - { type: "update", client: 0, path: "doc.md", content: "edited by local user" }, - - // Client 1 comes online first — delete is sent to server - { type: "enable-sync", client: 1 }, - { type: "sync", client: 1 }, - - // Client 0 comes online — receives remote-delete, then its - // local-update coalesces with it - { type: "enable-sync", client: 0 }, - { type: "sync" }, - { type: "barrier" }, - - // Both must converge - { type: "assert-consistent", verify: verifyState } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/rename-empty-file-loses-identity.test.ts b/frontend/deterministic-tests/src/tests/rename-empty-file-loses-identity.test.ts deleted file mode 100644 index 5d0c94a8..00000000 --- a/frontend/deterministic-tests/src/tests/rename-empty-file-loses-identity.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * BUG: Renaming an empty file offline causes delete+create instead of move. - * - * In vfs.ts reconcileWithDisk (line 802-805): - * if (fileHash === undefined || fileHash === EMPTY_HASH) { - * remainingNew.push(path); - * continue; - * } - * - * Empty files (hash === EMPTY_HASH) are excluded from hash-based move - * detection. When an empty file is renamed offline, the reconciliation - * treats it as: - * - Old path: missing file → delete - * - New path: new file → create - * - * This loses the document's identity (gets a new documentId on the server). - * The observable consequence is that the file appears as deleted+created - * rather than renamed, and version history is lost. - * - * This test verifies that both clients converge after an empty file - * rename. The file should exist at the new path on both clients. - */ -function verifyRenamedFile(state: ClientState): void { - assert( - state.files.size === 1, - `Expected 1 file, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}` - ); - assert( - !state.files.has("empty.md"), - "empty.md should not exist (was renamed)" - ); - assert( - state.files.has("renamed.md"), - "renamed.md should exist (renamed from empty.md)" - ); - assert( - state.files.get("renamed.md") === "", - `Expected empty content, got: "${state.files.get("renamed.md")}"` - ); -} - -export const renameEmptyFileLosesIdentityTest: TestDefinition = { - name: "Rename Empty File Loses Document Identity", - description: - "When an empty file is renamed offline, the reconciliation cannot " + - "detect it as a move (empty files are excluded from hash-based " + - "move detection). This causes delete+create instead of move, " + - "losing the document's server-side identity/history.", - clients: 2, - steps: [ - // Create and sync an empty file - { type: "create", client: 0, path: "empty.md", content: "" }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - { type: "assert-exists", client: 1, path: "empty.md" }, - - // Client 0 goes offline and renames - { type: "disable-sync", client: 0 }, - { type: "rename", client: 0, oldPath: "empty.md", newPath: "renamed.md" }, - - // Reconnect - { type: "enable-sync", client: 0 }, - { type: "sync" }, - { type: "barrier" }, - - // Both should have only renamed.md - { type: "assert-not-exists", client: 0, path: "empty.md" }, - { type: "assert-not-exists", client: 1, path: "empty.md" }, - { type: "assert-exists", client: 0, path: "renamed.md" }, - { type: "assert-exists", client: 1, path: "renamed.md" }, - { type: "assert-consistent", verify: verifyRenamedFile } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/rename-nested-path.test.ts b/frontend/deterministic-tests/src/tests/rename-nested-path.test.ts deleted file mode 100644 index 4f14c690..00000000 --- a/frontend/deterministic-tests/src/tests/rename-nested-path.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyNestedPath(state: ClientState): void { - const files = Array.from(state.files.keys()); - assert( - !files.includes("a.md"), - `a.md should not exist after rename to nested path, got: ${files.join(", ")}` - ); - assert( - files.includes("folder/subfolder/a.md"), - `Expected folder/subfolder/a.md to exist, got: ${files.join(", ")}` - ); - assert( - state.files.get("folder/subfolder/a.md") === "nested content", - `Expected nested file to have "nested content", got: "${state.files.get("folder/subfolder/a.md")}"` - ); -} - -export const renameNestedPathTest: TestDefinition = { - name: "Rename to Deeply Nested Path", - description: - "Client 0 creates a.md at the root, then renames it to folder/subfolder/a.md " + - "while offline. When Client 0 reconnects, the file should appear at the " + - "nested path on both clients. Tests that the system handles directory " + - "creation for deeply nested rename targets.", - clients: 2, - steps: [ - // Setup: create file at root and sync - { type: "create", client: 0, path: "a.md", content: "nested content" }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - { type: "assert-content", client: 1, path: "a.md", content: "nested content" }, - - // Client 0 goes offline and renames to nested path - { type: "disable-sync", client: 0 }, - { type: "rename", client: 0, oldPath: "a.md", newPath: "folder/subfolder/a.md" }, - - // Client 0 reconnects - { type: "enable-sync", client: 0 }, - { type: "sync" }, - { type: "barrier" }, - - // Original path gone, nested path exists - { type: "assert-not-exists", client: 0, path: "a.md" }, - { type: "assert-not-exists", client: 1, path: "a.md" }, - { type: "assert-exists", client: 0, path: "folder/subfolder/a.md" }, - { type: "assert-exists", client: 1, path: "folder/subfolder/a.md" }, - { type: "assert-consistent", verify: verifyNestedPath } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/rename-tracked-to-occupied-pending-path.test.ts b/frontend/deterministic-tests/src/tests/rename-tracked-to-occupied-pending-path.test.ts deleted file mode 100644 index bb168390..00000000 --- a/frontend/deterministic-tests/src/tests/rename-tracked-to-occupied-pending-path.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyResult(state: ClientState): void { - const files = Array.from(state.files.keys()).sort(); - // The rename of B.md to A.md overwrites A.md on disk. The pending - // create's content ("first file at A") is lost because the user - // chose to overwrite it. VFS.move fails (A.md occupied by pending - // create), so the fallback enqueues an update for B.md which fails - // (FileNotFoundError — B.md no longer exists on disk). - // - // After reconciliation: A.md's pending create reads the overwritten - // content ("tracked file B") from disk, and B.md is deleted - // (missing from disk). - // - // Result: A.md with "tracked file B" content. - assert( - state.files.size === 1, - `Expected 1 file, got ${state.files.size}: ${files.join(", ")}` - ); - assert( - state.files.has("A.md"), - `Expected A.md to exist. Files: ${files.join(", ")}` - ); - const content = state.files.get("A.md") ?? ""; - assert( - content === "tracked file B", - `Expected A.md to have "tracked file B", got: "${content}"` - ); -} - -/** - * BUG: Tests VFS.move failure when renaming a tracked file to a path - * occupied by a pending create. In syncer.ts, VFS.move is attempted - * but fails if the target path is occupied by a non-deleted-locally - * document. The move event falls back to an update at oldPath. - * - * When the user renames B.md to A.md, the filesystem overwrites A.md. - * The pending create's original content is lost from disk. After sync, - * only A.md survives with B.md's content. - */ -export const renameTrackedToOccupiedPendingPathTest: TestDefinition = { - name: "Rename Tracked File to Path Occupied by Pending Create", - description: - "Client creates A.md (pending, sync disabled) then renames B.md " + - "(tracked) to A.md. VFS.move should fail because A.md is occupied " + - "by the pending create. The rename overwrites A.md on disk, so " + - "only A.md survives with B.md's content.", - clients: 2, - steps: [ - // Setup: create B.md and sync it (becomes tracked) - { - type: "create", - client: 0, - path: "B.md", - content: "tracked file B" - }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - { - type: "assert-content", - client: 1, - path: "B.md", - content: "tracked file B" - }, - - // Disable sync on Client 0 - { type: "disable-sync", client: 0 }, - - // Create A.md (pending — sync disabled, not yet synced) - { - type: "create", - client: 0, - path: "A.md", - content: "first file at A" - }, - - // Try to rename tracked B.md to A.md (occupied by pending) - { type: "rename", client: 0, oldPath: "B.md", newPath: "A.md" }, - - // Re-enable sync — after reconciliation, A.md survives - { type: "enable-sync", client: 0 }, - { type: "sync" }, - { type: "barrier" }, - - // A.md exists with B.md's content (rename overwrite) - { type: "assert-consistent", verify: verifyResult } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/server-pause-concurrent-creates.test.ts b/frontend/deterministic-tests/src/tests/server-pause-concurrent-creates.test.ts deleted file mode 100644 index f997aafd..00000000 --- a/frontend/deterministic-tests/src/tests/server-pause-concurrent-creates.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyBothFilesPresent(state: ClientState): void { - const allContent = Array.from(state.files.values()).join("\n"); - assert( - allContent.includes("offline-alpha"), - `Missing content "offline-alpha". Files: ${JSON.stringify(Object.fromEntries(state.files))}` - ); - assert( - allContent.includes("offline-beta"), - `Missing content "offline-beta". Files: ${JSON.stringify(Object.fromEntries(state.files))}` - ); -} - -export const serverPauseConcurrentCreatesTest: TestDefinition = { - name: "Server Pause — Concurrent Creates From Both Clients", - description: - "The server is paused BEFORE either client creates anything. " + - "Client 0 creates fileA.md and Client 1 creates fileB.md — both HTTP " + - "requests stall because the server is frozen. After the server resumes, " + - "both creates should complete and both files should appear on both clients. " + - "This is a harder variant than the existing create-while-server-paused test " + - "because BOTH clients have stalled pending creates simultaneously, testing " + - "that the server correctly handles a burst of requests after SIGCONT and " + - "that idempotency keys prevent duplicate documents if retries occur.", - clients: 2, - steps: [ - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Pause the server FIRST — no requests can succeed - { type: "pause-server" }, - - // Both clients create different files while the server is frozen - { - type: "create", - client: 0, - path: "fileA.md", - content: "offline-alpha" - }, - { - type: "create", - client: 1, - path: "fileB.md", - content: "offline-beta" - }, - - // Resume the server — both pending creates should complete - { type: "resume-server" }, - - { type: "sync" }, - { type: "barrier" }, - - // Both files must exist on both clients - { type: "assert-exists", client: 0, path: "fileA.md" }, - { type: "assert-exists", client: 0, path: "fileB.md" }, - { type: "assert-exists", client: 1, path: "fileA.md" }, - { type: "assert-exists", client: 1, path: "fileB.md" }, - { - type: "assert-content", - client: 0, - path: "fileA.md", - content: "offline-alpha" - }, - { - type: "assert-content", - client: 1, - path: "fileA.md", - content: "offline-alpha" - }, - { - type: "assert-content", - client: 0, - path: "fileB.md", - content: "offline-beta" - }, - { - type: "assert-content", - client: 1, - path: "fileB.md", - content: "offline-beta" - }, - { type: "assert-consistent", verify: verifyBothFilesPresent } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/server-pause-rename-propagation.test.ts b/frontend/deterministic-tests/src/tests/server-pause-rename-propagation.test.ts deleted file mode 100644 index b4ada3a0..00000000 --- a/frontend/deterministic-tests/src/tests/server-pause-rename-propagation.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyRename(state: ClientState): void { - const files = Array.from(state.files.keys()); - assert( - !state.files.has("original.md"), - `Expected original.md to NOT exist after rename, got files: ${files.join(", ")}` - ); - assert( - state.files.has("renamed.md"), - `Expected renamed.md to exist after rename, got files: ${files.join(", ")}` - ); - const content = state.files.get("renamed.md") ?? ""; - assert( - content === "important data", - `Expected renamed.md content to be "important data", got: "${content}"` - ); -} - -export const serverPauseRenameTest: TestDefinition = { - name: "Server Pause Then Rename Propagation", - description: - "Client 0 creates original.md and both clients sync. The server is paused. " + - "Client 0 renames original.md to renamed.md while the server is frozen. " + - "After the server resumes, the rename should propagate to Client 1: " + - "original.md disappears and renamed.md appears with the same content. " + - "This tests that rename operations (which are update-with-oldPath on the " + - "HTTP layer) survive server outages and that Client 1 correctly applies " + - "the path change from the WebSocket broadcast.", - clients: 2, - steps: [ - // Setup: create file and sync both clients - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { - type: "create", - client: 0, - path: "original.md", - content: "important data" - }, - { type: "sync" }, - { type: "barrier" }, - { - type: "assert-content", - client: 1, - path: "original.md", - content: "important data" - }, - - // Pause the server, then rename on client 0 - { type: "pause-server" }, - { - type: "rename", - client: 0, - oldPath: "original.md", - newPath: "renamed.md" - }, - - // Resume the server — the stalled rename request should complete - { type: "resume-server" }, - - { type: "sync" }, - { type: "barrier" }, - - // original.md should be gone, renamed.md should exist on both - { type: "assert-not-exists", client: 0, path: "original.md" }, - { type: "assert-not-exists", client: 1, path: "original.md" }, - { type: "assert-exists", client: 0, path: "renamed.md" }, - { type: "assert-exists", client: 1, path: "renamed.md" }, - { type: "assert-consistent", verify: verifyRename } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/server-pause-resume.test.ts b/frontend/deterministic-tests/src/tests/server-pause-resume.test.ts deleted file mode 100644 index b1e09ebe..00000000 --- a/frontend/deterministic-tests/src/tests/server-pause-resume.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { TestDefinition } from "../test-definition"; - -export const serverPauseResumeTest: TestDefinition = { - name: "Server Pause and Resume", - description: - "Client 0 creates a file and syncs it to the server. The server is then " + - "paused (SIGSTOP), which may stall WebSocket broadcasts to Client 1. " + - "After the server resumes, both clients should converge.", - clients: 2, - steps: [ - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - - // Create a file, then immediately pause the server - { type: "create", client: 0, path: "resilient.md", content: "survives pause" }, - { type: "pause-server" }, - { type: "resume-server" }, - - // After resume, sync should eventually succeed - { type: "sync" }, - { type: "barrier" }, - - { type: "assert-exists", client: 0, path: "resilient.md" }, - { type: "assert-exists", client: 1, path: "resilient.md" }, - { - type: "assert-content", - client: 0, - path: "resilient.md", - content: "survives pause" - }, - { - type: "assert-content", - client: 1, - path: "resilient.md", - content: "survives pause" - }, - { type: "assert-consistent" } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/stale-doc-orphan-duplicate-content.test.ts b/frontend/deterministic-tests/src/tests/stale-doc-orphan-duplicate-content.test.ts deleted file mode 100644 index cac96e9c..00000000 --- a/frontend/deterministic-tests/src/tests/stale-doc-orphan-duplicate-content.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * BUG: Stale doc kept on disk creates duplicate content after create-merge. - * - * Found by: E2E test log analysis (log.log, process 672773) - * - * Root cause sequence: - * 1. Client 1 has document D1 tracked at path "target.md" - * 2. Client 0 renames D1 to "moved.md" on the server - * 3. Client 1 (offline) creates a new file at "moved.md" - * 4. Client 1 reconnects — the create is sent to the server - * 5. Server merges the create with D1 (at "moved.md") → MergingUpdate with D1 - * 6. ensureUniqueDocumentId finds D1 at "target.md" → stale doc - * 7. "target.md" was locally modified during the create's HTTP request - * → hasLocalChanges = true → file kept on disk, VFS record removed - * 8. On the next reconciliation, orphaned "target.md" is re-synced - * as a new document. Now BOTH "target.md" and "moved.md" contain - * the original content from D1 — violating the content-uniqueness - * invariant. - * - * The server pause is used to keep the create HTTP request in-flight - * while the local file at D1's old path is modified (step 7). - */ -function verifyNoDuplicateContent(state: ClientState): void { - const entries = [...state.files.entries()]; - - // The word "original" was D1's initial content. After the create-merge, - // it should appear in at most ONE file. If the stale orphan was re-synced - // as a separate document, "original" will appear in multiple files. - const filesContainingOriginal = entries.filter(([, content]) => - content.includes("original") - ); - - assert( - filesContainingOriginal.length <= 1, - `Content "original" found in ${filesContainingOriginal.length} files: ` + - `${filesContainingOriginal.map(([p]) => p).join(", ")}. ` + - `This means the stale doc orphan was re-synced, creating duplicate content.\n` + - `Files:\n${entries.map(([k, v]) => ` ${k}: "${v}"`).join("\n")}` - ); -} - -export const staleDocOrphanDuplicateContentTest: TestDefinition = { - name: "Stale Doc Orphan Creates Duplicate Content After Create-Merge", - description: - "When a create merges with an existing document, the stale VFS " + - "record is removed but the file is kept on disk (local changes). " + - "If the orphaned file is later re-synced as a new document, the " + - "original content appears in multiple files.", - clients: 2, - steps: [ - // ── Setup: both clients share D1 at "target.md" ── - { type: "create", client: 0, path: "target.md", content: "original" }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // ── Client 1 goes offline ── - { type: "disable-sync", client: 1 }, - - // ── Client 0 renames the document to a new path ── - // Server now has D1 at "moved.md" - { - type: "rename", - client: 0, - oldPath: "target.md", - newPath: "moved.md" - }, - { type: "sync", client: 0 }, - - // ── Client 1 (offline) creates a file at D1's new server path ── - // Client 1 doesn't know D1 was renamed there. - { - type: "create", - client: 1, - path: "moved.md", - content: "unrelated-content" - }, - - // ── Pause server to stall the create HTTP request ── - { type: "pause-server" }, - - // ── Enable sync on client 1 ── - // scheduleSyncForOfflineChanges runs: - // "target.md": D1, hash matches → no update - // "moved.md": no metadata → create scheduled - // The create HTTP request stalls (server frozen). - // enableSync waits up to 10 s for WebSocket then returns. - { type: "enable-sync", client: 1 }, - - // ── Modify D1's old path while the create is in-flight ── - // This makes hasLocalChanges = true when ensureUniqueDocumentId - // checks the stale doc at "target.md". - { - type: "update", - client: 1, - path: "target.md", - content: "original extra-edit" - }, - - // ── Resume server ── - // Create completes: server merges with D1 → MergingUpdate - // ensureUniqueDocumentId: D1 at "target.md" → stale doc - // hasLocalChanges("target.md"): "original extra-edit" ≠ "original" → true - // File kept, VFS record removed. - // - // WebSocket connects → second reconciliation detects orphaned - // "target.md" → re-synced as new document → DUPLICATE CONTENT. - { type: "resume-server" }, - - // ── Settle ── - { type: "sync" }, - { type: "sync" }, - { type: "barrier" }, - - // ── Verify: "original" must not appear in multiple files ── - { type: "assert-consistent", verify: verifyNoDuplicateContent } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/three-client-convergence.test.ts b/frontend/deterministic-tests/src/tests/three-client-convergence.test.ts deleted file mode 100644 index 0a522ccd..00000000 --- a/frontend/deterministic-tests/src/tests/three-client-convergence.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyAllContent(state: ClientState): void { - // All three creates at the same path should merge into a single file - assert( - state.files.size === 1, - `Expected 1 file after 3-way merge, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}` - ); - assert( - state.files.has("A.md"), - `Expected merged file at A.md, got: ${Array.from(state.files.keys()).join(", ")}` - ); - - const content = state.files.get("A.md") ?? ""; - assert( - content.includes("from-zero"), - `Expected merged content to include "from-zero", got: "${content}"` - ); - assert( - content.includes("from-one"), - `Expected merged content to include "from-one", got: "${content}"` - ); - assert( - content.includes("from-two"), - `Expected merged content to include "from-two", got: "${content}"` - ); -} - -export const threeClientConvergenceTest: TestDefinition = { - name: "Three Client Convergence", - description: - "Three clients all create the same file offline with different content. " + - "When all three enable sync, the server must merge all three versions " + - "and all clients must converge to the same state with all content preserved.", - clients: 3, - steps: [ - // All three create A.md offline with different content - { type: "create", client: 0, path: "A.md", content: "from-zero" }, - { type: "create", client: 1, path: "A.md", content: "from-one" }, - { type: "create", client: 2, path: "A.md", content: "from-two" }, - - // Enable sync on all three - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "enable-sync", client: 2 }, - { type: "sync" }, - { type: "barrier" }, - - // All three must converge and all content must be preserved - { type: "assert-consistent", verify: verifyAllContent } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/update-vs-remote-delete-data-loss.test.ts b/frontend/deterministic-tests/src/tests/update-vs-remote-delete-data-loss.test.ts deleted file mode 100644 index d84f3e11..00000000 --- a/frontend/deterministic-tests/src/tests/update-vs-remote-delete-data-loss.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * BUG: update + remote-delete = update, but execution deletes the file. - * - * In sync-events.ts coalesceFromUpdate (line 148-152): - * case "remote-delete": - * return current; // comment: "user edit takes precedence" - * - * The coalescing INTENT is correct: the user's edit should survive. - * But the EXECUTION doesn't match: - * - * 1. The coalesced "update" action calls executeSyncUpdateSendChanges() - * 2. This sends putText/putBinary to the server - * 3. The server's update_document handler checks if latest_version.is_deleted - * 4. Since the doc IS deleted, server returns FastForwardUpdate(isDeleted=true) - * 5. applyServerResponse checks response.isDeleted at line 296 - * 6. Calls applyRemoteDeleteLocally which DELETES the file! - * - * The user's edit is permanently lost despite the coalescing saying - * "user edit takes precedence." - * - * This test proves the data loss by having one client edit while another - * deletes, with the edit arriving at the event queue before the delete. - */ -function verifyUserEditPreserved(state: ClientState): void { - // The coalescing says "user edit takes precedence" so the file - // should ideally survive with the user's content. - // Current behavior: file is deleted (data loss). - // We test for convergence. - if (state.files.size > 0) { - assert( - state.files.has("doc.md"), - `Unexpected files: ${Array.from(state.files.keys()).join(", ")}` - ); - const content = state.files.get("doc.md")!; - assert( - content.includes("user edit"), - `Expected user's edit content, got: "${content}"` - ); - } -} - -export const updateVsRemoteDeleteDataLossTest: TestDefinition = { - name: "Update + Remote Delete Coalescing Data Loss", - description: - "When a user edits a file and then a remote-delete arrives, the " + - "coalescing produces 'update' (user edit takes precedence). But " + - "the server returns isDeleted=true, causing the client to delete " + - "the file — contradicting the coalescing intent.", - clients: 2, - steps: [ - // Setup: both clients have doc.md - { type: "create", client: 0, path: "doc.md", content: "original" }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both go offline - { type: "disable-sync", client: 0 }, - { type: "disable-sync", client: 1 }, - - // Client 0 edits the file (local-update queued first) - { type: "update", client: 0, path: "doc.md", content: "user edit on client 0" }, - - // Client 1 deletes the file - { type: "delete", client: 1, path: "doc.md" }, - - // Client 1 comes online first — delete sent to server - { type: "enable-sync", client: 1 }, - { type: "sync", client: 1 }, - - // Client 0 comes online — local-update already queued, - // then remote-delete arrives and coalesces: - // update + remote-delete = update (per coalescing) - { type: "enable-sync", client: 0 }, - { type: "sync" }, - { type: "barrier" }, - - // Both must converge to a consistent state - { type: "assert-consistent", verify: verifyUserEditPreserved } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/user-parenthesized-file-not-deleted.test.ts b/frontend/deterministic-tests/src/tests/user-parenthesized-file-not-deleted.test.ts deleted file mode 100644 index 42c6527b..00000000 --- a/frontend/deterministic-tests/src/tests/user-parenthesized-file-not-deleted.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -/** - * BUG FIX: User-created files with parenthesized names must not be deleted. - * - * The duplicate content detection in step 7 of reconciliation uses a regex - * that matches files like "Chapter (1).md". This should only delete files - * created by ensureClearPath, not user-intentionally-created files. - * - * Note: the two files MUST have different content, because the server - * merges deconflicted-path creates when the content is identical to the - * base-path document. - */ -function verifyBothFilesExist(state: ClientState): void { - assert( - state.files.size === 2, - `Expected 2 files, got ${state.files.size}: ${Array.from(state.files.keys()).join(", ")}` - ); - assert( - state.files.has("Chapter.md"), - "Expected Chapter.md to exist" - ); - assert( - state.files.has("Chapter (1).md"), - "Expected Chapter (1).md to exist" - ); -} - -export const userParenthesizedFileNotDeletedTest: TestDefinition = { - name: "User-Created Parenthesized Files Not Deleted", - description: - "A user-created file like 'Chapter (1).md' should not be silently " + - "deleted by the duplicate content detection heuristic. Uses " + - "different content to avoid server-side deconfliction merge.", - clients: 2, - steps: [ - // Client 0 creates both files with DIFFERENT content - // (same content triggers server-side deconfliction merge) - { - type: "create", - client: 0, - path: "Chapter.md", - content: "chapter one" - }, - { - type: "create", - client: 0, - path: "Chapter (1).md", - content: "chapter one notes" - }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - - // Both files should survive on both clients - { type: "assert-consistent", verify: verifyBothFilesExist } - ] -}; diff --git a/frontend/deterministic-tests/src/tests/write-write-conflict.test.ts b/frontend/deterministic-tests/src/tests/write-write-conflict.test.ts deleted file mode 100644 index 873a010b..00000000 --- a/frontend/deterministic-tests/src/tests/write-write-conflict.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { ClientState, TestDefinition } from "../test-definition"; -import { assert } from "../utils/assert"; - -function verifyMergedContent(state: ClientState): void { - assert(state.files.size === 1, `Expected 1 file, got ${state.files.size}`); - assert(state.files.has("A.md"), "Expected A.md to exist"); - const content = state.files.get("A.md") ?? ""; - assert( - content.includes("hello") && content.includes("world"), - `Expected A.md to contain both "hello" and "world", got: "${content}"` - ); - // Verify no duplication — each word should appear exactly once - const helloCount = content.split("hello").length - 1; - const worldCount = content.split("world").length - 1; - assert( - helloCount === 1, - `Expected "hello" to appear once, appeared ${helloCount} times in: "${content}"` - ); - assert( - worldCount === 1, - `Expected "world" to appear once, appeared ${worldCount} times in: "${content}"` - ); -} - -export const writeWriteConflictTest: TestDefinition = { - name: "Write/Write Conflict", - description: - "Two clients simultaneously create the same file with different content. " + - "The system should resolve the conflict and both clients should converge.", - clients: 2, - steps: [ - { type: "create", client: 0, path: "A.md", content: "hello" }, - { type: "create", client: 1, path: "A.md", content: "world" }, - { type: "enable-sync", client: 0 }, - { type: "enable-sync", client: 1 }, - { type: "sync" }, - { type: "barrier" }, - { type: "assert-consistent", verify: verifyMergedContent } - ] -};