diff --git a/docs/clustering.md b/docs/clustering.md
index 3d2911524abc53655354da4c5903c84de6ef4e2a..0e9a547feb6dbb4809d98f3fc4be6ef2bfa7ed6c 100644
--- a/docs/clustering.md
+++ b/docs/clustering.md
@@ -62,7 +62,7 @@ picodata run --instance-id iN --listen iN --peer i1
 
 # Обработка запросов
 
-### extern "C" fn join()
+### \#\[proc\] fn raft_join()
 
 Значительная часть всей логики по управлению топологией находится в хранимой процедуре `raft_join`. Аргументом для нее является следующая структура:
 
@@ -120,3 +120,24 @@ struct Peer {
 - Генерировать значение `raft_id` может только лидер Raft-группы. Ожидание `ConfChangeV2` лидерства не требует.
 - Помимо всевозможных идентификаторов, ответ содержит список голосующих членов Raft-группы. Они понадобятся новому инстансу чтобы знать адреса соседей и нормально с ними общаться.
 - Также ответ содержит параметр `box_replication`, который требуется для правильной настройки репликации.
+
+# Логика conf_change_loop
+
+Все узлы Raft в кластере делятся на два типа: голосующие (`voter`) и неголосующие (`learner`). На каждом инстансе кластера присутствует поток, управляющий конфигурацией Raft-группы (составом `voters` / `learners`). Реальные изменения тем не менее может генерировать только лидер, на остальных инстансах этот поток спит и ничего не делает.
+
+Поток представляет собой бесконечный цикл. На каждой итерации выполняется проверка, что состав `voters` / `learners` соответствует состоянию инстансов, и при необходимости эти изменения пачкой записываются в Raft-журнал:
+
+- Инстансы в статусе `health: Loading` довавляются как неголосующие.
+- Если есть возможность, `Offline` инстансы передают право голоса другим `Online`.
+- Если общее количество голосующих инстансов оказывается меньше целевого, `Online` инстансы получают право голоса.
+
+Количество голосующих узлов в кластере не настраивается и зависит только от общего количества инстансов. Если инстансов 1 или 2, то голосующий узел один. Если инстансов 3 или 4, то таких узлов три. Для кластера с 5 или более инстансами — пять голосующих узлов.
+
+# Graceful shutdown
+
+Чтобы выключение прошло штатно и не имело негативных последствий необходимо следующее:
+
+- Инстанс не должен оставаться воутером, пока есть другие онлайн кандидаты.
+- Инстанс не должен оставаться лидером.
+
+Чтобы этого добиться, каждый инстанс на `on_shutdown` триггер отправляет лидеру запрос `UpdatePeerRequest{ health: Offline }`. Непосредственно изменением роли `voter` -> `learner` занимается отдеьный поток на лидере (тот самый `conf_change_loop`), инстанс только дожидается его применения.
diff --git a/src/traft/node.rs b/src/traft/node.rs
index acac14e88d63793e246040feab0ce732c9ac3d28..ce99135ec8ff786c2593a0af9b61edc7c9146e6f 100644
--- a/src/traft/node.rs
+++ b/src/traft/node.rs
@@ -317,6 +317,8 @@ impl Node {
         .recv::<Peer>()
     }
 
+    /// Only the conf_change_loop on a leader is eligible to call this function.
+    ///
     /// **This function yields**
     fn propose_conf_change(
         &self,