feat(syncguard): protect workspace during substantial CouchDB catch-up

This commit is contained in:
Artem Ovchinnikov committed 2026-10-21 21:25:25 +03:00
1 parent bca2f2dcc6
commit f7c7ef364c
9 files changed
+832 -6

No files matched your search

@@ -27,6 +27,8 @@ describe("SyncGuardState", () => {
expect(guard.presentation.phase).toBe("observing");
guard.receivedDocuments(["f:three"]);
expect(guard.presentation).toEqual({ phase: "transferring", receivedFiles: 3 });
guard.receivedDocuments(["f:four", "f:four"]);
expect(guard.presentation.receivedFiles).toBe(4);
guard.replicationStatusChanged("COMPLETED");
guard.finiteActivityChanged(false, 2, 10);
expect(guard.presentation.phase).toBe("applying");
@@ -53,6 +55,51 @@ describe("SyncGuardState", () => {
expect(guard.presentation.phase).toBe("idle");
});
it("does not unlock when a new pull starts while applying an earlier completed transfer", () => {
const { guard } = setup();
guard.resume(true, 1);
guard.finiteActivityChanged(true, 0, 0);
guard.receivedDocuments(["f:first"]);
guard.replicationStatusChanged("COMPLETED");
guard.finiteActivityChanged(false, 0, 1);
expect(guard.presentation.phase).toBe("applying");
guard.finiteActivityChanged(true, 0, 100);
expect(guard.presentation.phase).toBe("transferring");
guard.tick(0, 10_000);
expect(guard.presentation.phase).toBe("transferring");
guard.finiteActivityChanged(false, 0, 10_001);
expect(guard.presentation.phase).toBe("interrupted");
});
it("can recover an interrupted catch-up when a new pull succeeds", () => {
const { guard } = setup();
guard.resume(true, 1);
guard.finiteActivityChanged(true, 0, 0);
guard.receivedDocuments(["f:first"]);
guard.finiteActivityChanged(false, 0, 10);
expect(guard.presentation.phase).toBe("interrupted");
guard.finiteActivityChanged(true, 0, 20);
expect(guard.presentation.phase).toBe("transferring");
guard.replicationStatusChanged("COMPLETED");
guard.finiteActivityChanged(false, 0, 30);
guard.tick(0, 2030);
expect(guard.presentation.phase).toBe("idle");
});
it("does not declare the Vault ready after a failed received-file reflection", () => {
const { guard } = setup();
guard.resume(true, 1);
guard.finiteActivityChanged(true, 0, 0);
guard.receivedDocuments(["f:first"]);
guard.reflectionFailed();
guard.replicationStatusChanged("COMPLETED");
guard.finiteActivityChanged(false, 0, 10);
guard.tick(0, 10_000);
expect(guard.presentation.phase).toBe("interrupted");
guard.bypass();
expect(guard.presentation.phase).toBe("idle");
});
it("does not confuse a stale completed status with this catch-up", () => {
const { guard } = setup();
guard.resume(true, 1);
@@ -88,4 +135,15 @@ describe("SyncGuardState", () => {
guard.receivedDocuments(["f:one"]);
expect(guard.presentation.phase).toBe("transferring");
});
it("applies a lowered threshold while observing without unlocking an already protected Vault", () => {
const { guard } = setup();
guard.resume(true, 20);
guard.finiteActivityChanged(true, 0, 0);
guard.receivedDocuments(["f:one", "f:two", "f:three"]);
guard.updateThreshold(3);
expect(guard.presentation.phase).toBe("transferring");
guard.updateThreshold(200);
expect(guard.presentation.phase).toBe("transferring");
});
});