Plan canonical history reconciliation on the shared Merkle engine

This commit is contained in:
evgeny committed 2026-10-26 18:27:26 +01:00
1 parent 5ae5fd066d
commit d20f31c165
2 files changed
+177 -4

No files matched your search

+168
View File
@@ -624,3 +624,171 @@ BAD_FORMAT, DEFERRED, ERROR), сохранение корректной суще
Эти места не включены в доказанное покрытие текущего исправления. GUI/Android/Windows/FreeBSD не запускались.
Следующий этап аудита требует отдельного исправления финального критерия; по AGENTS.md
дальнейшее расширение текущей реализации остановлено на этом замечании.
12. План устранения ложной сходимости истории и проверка предпосылок — 26.10.2026
Запрос пользователя: продумать исправление и его корректность. Runtime-код в этом этапе не менялся.
Повторно прочитаны относящиеся к обмену структуры/функции db_sync, общий merkle_sync,
контракт data_ops, запуск/остановка chat_sync, создание канала, callback докачки медиа и sleep policy.
Почему заменить одну ветку на restart недостаточно:
1. db_handle_send_data объявляет sync_state=2/done сразу после отправки собственного SYNC_DONE,
ещё до получения подтверждённого равенства. db_handle_init_resp имеет отдельный путь завершения.
2. db_handle_init_resp исследует только последние51 позицию через16 интервалов.
Самая ранняя несовпавшая проба не является первым отличием или доказанным общим префиксом.
3. chain_hash включает id, который не входит в ключ записи или подпись автора.
Разные id одного подписанного сообщения сохраняются при duplicate и не исправляются пересылкой.
4. OFFSET относится к изменяемой таблице; поздний PUSH/DELETE сдвигает позиции.
В старом wire нет идентификатора раунда, запросы/ответы не проверяются по фазе.
5. Сравнение8 байт не заменяет проверку полного SHA256 при объявлении успеха.
Дополнительный изолированный стенд /tmp/db-sync-completion-design.c/.mk, сборка через tests/Makefile:
использованы настоящие SQLite/OpenSSL/uasync и private-функции из штатного integrity test.
Только собственные временные записи, вызовы локальных обработчиков, без сетевых пакетов.
- Две таблицы одного group_id содержат одинаковые timestamp/author/data/signature, но id1 и900.
Обе db_sync_chain_verify возвращают0, последние хеши различны. Передача локальному обработчику
count1/чужого хеша вызывает done1 и state2: ложное завершение подтверждено runtime.
- По200 записей, различие только в первой; остальные199 записей и id совпадают.
Фактические хеши по текущим sparse-пробам дают fm148. Весь хвост148..199 — дубликаты;
его повторная вставка в обе таблицы сохраняет различие корней. Простое повторение старого обмена
не имеет условия прогресса. Симулировался выбор sparse-проб и повтор хвоста, не полный сетевой раунд.
- Exit0, tracked allocations восстановлены. /tmp/db-sync-completion-design-build.log и
/tmp/db-sync-completion-design.log. Это диагностика существующего кода, не тест новой реализации.
Варианты:
А. Сохранить собственный bulk-протокол: потребуются канонический хеш без id, поиск настоящего общего
префикса либо полный fallback с0, устойчивый курсор, два явных прохода, round/request/state,
проверка revision и подтверждённого корня, backpressure и корректная отмена.
Это второй самостоятельный автомат с теми же обязанностями, что уже выполняет merkle_sync.
Б. Рекомендуемый: история как MT_DATA_MESSAGES в общем merkle_sync. db_sync остаётся моделью хранения,
публичным API истории и PUSH/ACK. Старые INIT/RESP/SEND_DATA/SYNC_DONE и цепной recalc удаляются
после подключения новой модели. Два bulk-протокола одновременно не поддерживаются.
Это архитектурный перенос, а не локальная замена ветки SYNC_DONE; отдельное одобрение реализации
не подразумевается самим запросом продумать план.
Конкретный план вариантаБ:
1. Канонический набор истории.
Namespace — числовой channel_id; dataset отдельный от MEMBERS/FEEDBACK.
Ключ — существующий message_id[32], проверяемый вычислением из group_id/timestamp/signature.
Лист и страница используют один и тот же отбор записей и порядок message_id по полным32 байтам.
Канонические байты записи: timestamp(BE64),author(BE64),data_len(BE32),точные data,signature64;
message_id связывает запись с каналом. Хеш листа включает ключ и эти однозначно кодируемые байты.
id,flags,local_attrs,delivery_chain/delivered_peers и прочие локальные поля в корень не входят.
Существующие подписи и идентичности сообщений не меняются; переподписывать историю не нужно.
Авторство старой записи не фильтруется текущим ACTIVE-правилом feedback. Допуск передающего пира
и проверка исторической подписи — разные проверки; действующие права не расширяются.
2. Индекс и границы SQL-транзакций.
Добавить индекс для выбора листа и keyset-страниц; bucket — старшие25 бит ключа.
Первая активация/restart восстанавливает производное дерево из сохранённых записей до снятия gate.
Индекс строится ограниченными порциями, без долгой SQLite-транзакции между сетевыми событиями;
ошибка оставляет канал неготовым, не публикует неполный корень как пустой.
Ожидание неизвестного ключа автора — отдельное от операционного отказа состояние: строки сохраняются,
готовность истории откладывается, но запуск чата не падает. После поступления общего ключа, в том числе
через другой канал, индексация/попытка возобновляется без перезапуска сервиса.
Каждый путь изменения содержимого (локальная вставка, PUSH, bulk page, доказанная порча, TTL)
сохраняет строки и пересчитывает затронутые Merkle-пути в одной транзакции.
Только после COMMIT меняются runtime-состояние/next_id и вызываются changed/on_insert.
flags/local_attrs/ACK не меняют набор и не запускают Merkle.
Перестройка формата должна сохранять подписанные строки и локальные атрибуты; DROP истории недопустим.
3. Adapter data_ops.
get_page — WHERE bucket=? AND message_id>after ORDER BY message_id LIMIT..., с учётом размера пакета.
Применение проверяет весь формат страницы, ключи/курсоры, подписи и достоверность identity до COMMIT.
Одна страница атомарна: частичная запись/индекс/события при SQL или OpenSSL failure запрещены.
apply_items не вызывает каскад PUSH и не отправляет пакеты из callback. Для локальной вставки/PUSH
после COMMIT вызывается changed; bulk тоже уведомляет о реальном изменении, чтобы оно дошло другим пирам.
Неизвестный ключ/ошибка операции прерывают попытку без удаления и ложной пустой страницы.
BAD_SIGNATURE/BAD_FORMAT относится к проверенным сохранённым байтам и допускает согласованную очистку.
Хеширование/чтение страницы сами не удаляют строки из открытого курсора и не запускают рекурсивный update.
Если там найдена сохранённая порча, чтение завершается ошибкой; очистка выполняется отдельно
после закрытия курсора в проверяемой транзакции, обновляет индекс и запускает повтор.
4. Управление раундом и успехом.
Используются существующие round/request/state, два pull-прохода, CHECK/DONE, полный32-байтовый корень,
revision, deadline и очередь с waiter. Ошибка или повтор неизменной пары разных корней — не успех.
done для db_sync/media поступает только от MT_OK; постановка пакета в очередь и конец страницы
не подтверждают завершение истории. Последующие изменения вновь делают сессию dirty.
Успех означает подтверждённую контрольную точку данного раунда, не вечную глобальную синхронность
при продолжающейся записи. Старый round/request не изменяет данные/позиции, не продлевает deadline
и не завершает новый запрос. Эти свойства нужно проверить именно для подключённой модели истории.
5. Интеграция, права и владение.
Регистрировать MESSAGES после создания общего движка при chat startup; data_ctx — стабильный inst,
канал ищется по namespace, указатель на DB_SYNC_INSTANCE не удерживается движком после remove.
Операционный отказ регистрации/индексации диагностируется и откатывает незавершённое подключение;
ожидание ключа не приравнивается к ошибке. При restart заново восстановить дерево и подписки,
даже если ETCP уже UP и повторного события UP не будет. До MEMBERS и READY история остаётся gated.
validate_peer проверяет активную таблицу/готовность/членство. Изменение допуска после COMMIT
переоценивает и MESSAGES, отменяя недопущенный раунд без потери чужих datasets.
Нужна узкая отмена по(dataset,namespace), а для sleep — по(dataset,peer): cancel_ns/cancel_peer сейчас
затрагивают сразу MEMBERS/FEEDBACK. Remove канала отменяет запросы истории до освобождения состояния.
Порядок stop учитывает, что member_sync_destroy уничтожает общий движок раньше удаления db_sync instances.
Публичные проверки цепочки заменяются явными проверкой истории/чтением полного Merkle-корня;
тесты обновляются, старые chain API с неожиданно изменённой семантикой не сохраняются.
Мост старых done callbacks требует безопасного удаления подписок во время dispatch; кешировать
next на освобождаемом элементе нельзя. Проверить удаление текущего/следующего callback и канала.
6. Sleep/sound и PUSH/ACK — обязательная часть, не скрытое изменение поведения.
Сейчас общий движок запрещает start при peer_sleep_phase, а chat_sync при sleep отменяет все datasets.
db_sync допускает важный чат при sound_on=1. При переносе это нельзя молча потерять.
Ввести предикат разрешённой работы модели: default для MEMBERS/FEEDBACK сохраняет нынешний запрет сна;
для MESSAGES использует существующий db_sync_peer_throttled. Проверять его при старте, автоповторе
после changed и обработке события sleep/sound, отменяя только запрещённые сессии.
Awake и включение sound_on повторно подключают допустимые READY-сессии, даже без нового UP;
одного changed недостаточно, если предыдущая отмена освободила подписку.
PUSH и ACK(message_id) продолжают текущую адресную доставку; собственный round не требуется ACK,
потому что он подтверждает конкретную неизменяемую запись. Очередь не считается доставкой.
Отказ PUSH не теряет dirty-изменение Merkle. Bulk-совпадение корня само по себе не увеличивает
delivered_peers: этот счётчик сохраняет текущую семантику индивидуальных ACK, без подтверждения
более поздних записей старым результатом раунда.
7. TTL и очистка.
До удаления старой цепочки исправить сравнение timestamp(ms) с cutoff сейчас в микросекундах;
использовать то же определение времени, что при создании timestamp, и исключить unsigned underflow.
Сохранить отдельную политику удаления локальных неподтверждённых записей, без глобальных tombstones.
TTL удаляет строки и обновляет MESSAGES-пути атомарно; изменившаяся история уведомляет движок после COMMIT.
Удаление хорошей копии из-за ошибки проверки запрещено прежним контрактом.
Почему этот план логически сходится:
- Равенство набора записей даёт одинаковые корни независимо от локальных id, порядка получения,
delivery counters или local_attrs. Изменение подписанного содержимого меняет канонические байты.
- В пределах листа after — полный ключ неизменяемой записи, а не OFFSET; вставка до курсора не сдвигает
уже читаемый хвост. Если её пропустил текущий проход, различие финальных корней требует нового раунда.
- При конечном наборе допустимых записей, прекращении внешних изменений, доступных ключах/ресурсах,
доставке пакетов и отсутствии криптографических коллизий первый pull добавляет записи второго узла
первому, обратный — объединение второму. Пропущенные из-за изменения дерева листья дочитываются
следующим раундом; принимать разные корни для окончания не требуется.
- Ошибки формата/операций и неизвестные зависимости не кодируются совпадением; repeated-root conflict
завершает попытку ошибкой, сохраняя данные. Поэтому нет бесконечного успешного restart без прогресса.
- Сохранённые данные и индекс меняются атомарно. Предположение доказательства — все изменения модели
проходят этот путь и производный индекс согласован; произвольная порча SQLite вне модели не становится
автоматически обнаружимой без чтения/проверки соответствующих записей. Startup и читаемые страницы
проверяются явно; непрерывная проверка всех байтов хранилища этим планом не обещается.
- Контрольная точка допускает последующую запись/отзыв/TTL: они помечают дерево dirty или отменяют
доступ, а не используют старый MT_OK для подтверждения новых данных.
Проверки при реализации (требования, сейчас не запускались для новой модели):
- Равный count и разные наборы: отличие в начале/середине/конце истории, >200 и несколько тысяч записей;
исходные ложные завершения воспроизводятся до исправления и исчезают после него.
- Полностью одинаковые подписанные записи с разными id/local_attrs/доставкой: одинаковый полный корень.
- Пустые таблицы; по одному сообщению; два автора с одним timestamp; несколько страниц одного листа,
пакет заполнен раньше лимита записей, дубликаты и повтор последней страницы.
- PUSH/вставка до и после курсора; третий узел; удаление/TTL; изменения между CHECK и отправкой DONE.
- Старые round/request, неожиданные фазы, отмена/повтор, DOWN/REINIT/restart, старый UP без нового события.
- SQL prepare/bind/step/commit, BLOB NOMEM, хеш/VerifyInit, очередь/таймер/waiter: нет частичных данных,
ложного MT_OK, потери корректных строк или callback после cancel/free.
- ACTIVE-допуск пира, отзыв/возврат, сохранение старых сообщений исключённого автора; различие sound_on/off
при сне, изменение sound в ходе раунда, восстановление после пробуждения.
- Недостающий исторический ключ не роняет startup и не стирает записи; его получение через другой
канал возобновляет индексацию и обмен без restart. Операционный SQL/crypto error имеет отдельный исход.
- Сохранность прежней истории/подписей/локальных атрибутов при смене схемы, удаление только derived-chain
формата, startup index failure, остановка и повторный запуск чата с работающим UTUN.
- Корректные индивидуальные ACK без двойного счётчика; media backfill вызывается только при MT_OK;
callback отписывает себя/следующий callback и удаляет канал без UAF.
- Общий check.sh со STUN и целевые ASAN/UBSan/LSan после реализации.
Статус: план вариантаБ сформирован и логически проверен при указанных предположениях;
новая модель и её тесты ещё не реализованы. Перенос нельзя объявлять проверенным текущими203/203
и8/8: эти результаты относятся к уже выполненному исправлению раздела11.
+9 -4
View File
@@ -98,10 +98,15 @@
[ ] **db_sync: финальная сверка принимает разные хеши при равном count** — замечено при исправлении26.10.2026.
db_handle_sync_done явно выставляет synced и вызывает done даже при chain_hash mismatch.
Нужно определить корректный повтор/поиск расхождения, проверить старые пакеты после reset;
wire не содержит ID раунда. Отдельно остаётся аудит TTL (единицы времени и recalc) и callbacks.
Нового runtime-воспроизведения этих случаев нет; текущие исправления удаления/доставки ниже завершены.
Расширение остановлено по AGENTS.md; [member_merkle_audit_findings.txt](member_merkle_audit_findings.txt), раздел11.
Дополнительный локальный стенд подтвердил ложный done и разные корни одинаковых подписанных записей
с разными неподписанными id. Для200 записей с отличием в первой sparse-поиск даёт fm148;
выбранный хвост состоит из дубликатов, простой повтор не сходится. Стенд exit0, без сетевых пакетов,
/tmp/db-sync-completion-design.c/.mk, лог /tmp/db-sync-completion-design.log.
План: история как MT_DATA_MESSAGES в общем Merkle, канонический набор без local id, атомарный индекс,
полный корень/раунды/курсор; оставить PUSH/ACK. Учесть startup/stop, права, callback dispatch и sleep/sound
(общий движок сейчас запрещает сон, db_sync допускает sound_on), исправить TTL ms/us и индекс после DELETE.
План отдельно обосновывает сходимость, предпосылки и регрессии; runtime не менялся, перенос не реализован.
[member_merkle_audit_findings.txt](member_merkle_audit_findings.txt), раздел12.
[+] **db_sync: отказ проверки/битый дубликат удаляли корректную историю** — исправлено26.10.2026 после rebase на03b05094.
VALID/BAD_SIGNATURE/BAD_FORMAT/DEFERRED/ERROR разделены; перед DELETE проверяется сама сохранённая копия.