diff --git a/doc/member_merkle_audit_findings.txt b/doc/member_merkle_audit_findings.txt index 30512b0a..11b15234 100644 --- a/doc/member_merkle_audit_findings.txt +++ b/doc/member_merkle_audit_findings.txt @@ -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. diff --git a/doc/tasks.md b/doc/tasks.md index 56313627..359afd39 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -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 проверяется сама сохранённая копия.