Files
utun2/doc/dm_audit_findings.txt
T

610 lines
61 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
DM — аудит документации и контрактов, 26–27.10.2026
База: /home/user/utun2, HEAD 03b050949cf36cc4868324be94a54eaddfdd494c.
Задание: /home/user/utun-doc-audit-checklist.txt, раздел src/dm/ и связанные документы.
Текущий статус на 27.10.2026: аудит контрактов DM завершён, 10/10 файлов [V].
Итог — раздел12; разделы1–11 сохраняют историю промежуточных проверок и исправлений.
1. Объём и статус
Полностью прочитаны dm_core.c/.h, dm_crypto.c/.h и dm_history.c/.h, doc/dm_arch.md,
doc/pm.txt и doc/pm_invites.md. Просмотрены заголовки media/mailbox owners, lifecycle
в utun_instance и участок media_forget_prepare/committed. Это не полный аудит этих owners.
dm_crypto.h [V]; dm_core.h/dm_history.h/doc/dm_arch.md [~].
dm_mailbox.h/dm_mailbox_media.h/dm_media.h/dm_media_priv.h сохраняют [ ].
doc/pm_invites.md сохраняет [~]; doc/pm.txt [ ]: нормативные требования прочитаны,
но соответствие всей доставки и хранения пока не проверено.
Уточнены контракты nonce, native-endian wire/длин, lifecycle, commit исходящей задачи,
JSON и локального удаления/поколений. В pm_invites исправлен формат события результата:
36 байт с generation вместо прежних 28. Платформенные реализации целиком не проверялись.
2. Исходный дефект: ошибка загрузки сбрасывает счётчики существующей беседы
dm_conv_load возвращает -1 при отсутствии строки и при ошибках prepare/step,
повреждённых ключах либо отказе X25519. dm_start проверяет только ==0, а иначе
обнуляет dm_conv, заполняет ключи и сохраняет через dm_conv_save.
ON CONFLICT обновляет last_out_seq/last_in_seq/last_ts из обнулённой структуры.
Ошибка чтения тем самым превращается в успешную запись с потерей счётчиков.
Проверка использует настоящие utun_core/chat_service, SQLite и OpenSSL, отдельные
БД в /tmp, штатные dm_start/dm_send/dm_accept_message. Исходные сообщения принимаются
обычным публичным API. SQLite authorizer однократно отказывает в чтении
dm_conversations.peer_node_id; последующие операции разрешены. Production-файлы,
архивы и объекты для воспроизведения не подменялись.
Положительный контроль: повторный dm_start без отказа сохраняет out=1,in=2.
При одном SQLITE_DENY: dm_conv_load пишет ERROR с точной причиной, затем dm_start
пишет INFO started, возвращает 0; out=1->0,in=2->0, обе строки dm_messages остаются.
Проверка ожидаемого отказа/сохранности даёт FAIL, exit2; tracked allocations0->0.
Существующей диагностики вместе с журналом инъекции и SQL-снимком достаточно
для однозначного вывода: это ошибка ветвления, не ошибка расшифровки или транспорта.
Последствия: пока исходящая строка seq=1 сохранена, новая отправка того же номера
будет конфликтовать с историей. После локального удаления история очищается,
а сброшенный счётчик сохраняется; повтор seq при неизменном ключе воспроизведёт
детерминированный nonce. Это вывод из кода, повторная отправка после сбоя в стенде
не выполнялась. Раскрытие содержимого/эксплуатация не проверялись.
В dm_accept_message и dm_invite_trampoline также смешаны отсутствие и ошибка
dm_conv_load; их отказные сценарии в этом этапе отдельно не воспроизводились.
Простой план исправления: выделить результаты FOUND/NOT_FOUND/ERROR для загрузки;
считать NOT_FOUND только завершённый SELECT без строки, ошибки логировать и
передавать без UPSERT/события успеха. Просмотреть все callers, включая dm_media;
при найденной беседе не обновлять seq из пустой структуры. Регрессии должны
проверить prepare/step/короткие ключи/crypto, сохранность полей и generation/hidden,
повторный запуск, успешное создание отсутствующей беседы и нормальное продолжение seq.
Новый формат протокола или БД для исправления не требуется.
3. Проверки и артефакты первоначального аудита, до исправления
Сборка штатных шести целей через tests/Makefile — PASS.
test_dm/test_dm_history/test_dm_e2e/test_dm_invite/test_dm_media/test_dm_worker:6/6 PASS.
Полные логи прочитаны; ERROR в штатных отрицательных проверках ожидаемы.
Worker:24 запуска,49 heartbeat,max_gap11.3мс. Media:phase9 PASS.
Эти тесты не покрывают подтверждённый отказ загрузки беседы.
/tmp/dm-audit-baseline-build.log
/tmp/dm-audit-baseline-results.log
/tmp/dm-audit-baseline-test_dm{,_history,_e2e,_invite,_media,_worker}.log
/tmp/dm-audit-load-failure.c и /tmp/dm-audit-load-failure.mk
/tmp/dm-audit-load-failure-build.log
/tmp/dm-audit-load-failure.log (контроль PASS, сохранность при отказе FAIL, exit2)
Первый запуск стенда в sandbox остановился до DM на запрете netlink-сокета.
Окончательные стенд и штатные тесты запущены с разрешением вне sandbox;
сетевые настройки хоста не менялись. Пользовательская БД не использовалась.
Рабочий алгоритм, протокол, БД и штатные тесты не менялись: только документы/комментарии.
Новые полный check.sh, ASAN/UBSan/LSan, GUI/Android/Windows/FreeBSD не запускались.
По AGENTS.md:10 аудит остановлен на подтверждённом дефекте до его исправления;
открытая задача внесена в doc/tasks.md. Коммит/push не выполнялись.
4. Исправление по запросу пользователя — 26.10.2026
dm_conv_load теперь возвращает FOUND/NOT_FOUND/ERROR. Только SQLITE_DONE без строки
и с успешным finalize означает NOT_FOUND; SQL-ошибки prepare/bind/step/finalize,
короткие ключи и отказ X25519 означают ERROR. Полная структура выдаётся только
после успешного завершения загрузки/вывода ключа. Лог содержит conv/rc/SQL-причину,
успешная загрузка с seq доступна в TRACE, отсутствие записи — DEBUG.
Проверены все5 callers: dm_start и dm_accept_message возвращают ошибку без
создания/сохранения/открытия беседы, dm_invite_trampoline завершает запрос
DM_INVITE_START_ERROR без повторного dm_start; dm_send и JSON требуют FOUND.
В dm_start также перестала игнорироваться ошибка сохранения выбранной области.
Медиа не вызывает dm_conv_load и не создаёт беседу вместо ошибочной загрузки:
dm_send_file отклоняет ошибки чтения/ключей до регистрации worker/файлов.
Протокол и формат БД не менялись.
Постоянная Linux-регрессия test_dm_load включена в check_PROGRAMS.
35 комбинаций5 callers×7 отказов для видимой беседы, ещё35 после локального удаления,
4 отказа отдельной media-подготовки и5 отказов открытия отсутствующей беседы:79 PASS.
Отказы:prepare/bind/step/finalize,короткие X25519/Ed25519 BLOB,EVP_PKEY_derive.
Проверяются SHA256 всех полей7 DM-таблиц,sqlite3_total_changes,autocommit,
отсутствие событий успеха и неизменённый буфер квитанции при отказе приёма.
Нормальные контроли:создание отсутствующей беседы,повторное открытие после restart,
продолжение исходящего seq1->2 и приём впервые доставленного seq1 после seq2.
Запрос invite проверяется на настоящей собственной CHAT-группе; общая доставка
и создание беседы при первом входящем сообщении покрываются штатными E2E/media-тестами.
Новая регрессия с прежним dm_core.c из HEAD ожидаемо FAIL на первом prepare-отказе:
ошибка -> dm_start0,дополнительные SQL-write/CONV_UPDATED;cleanup allocations0->0.
Использовались отдельные /tmp/old-core.o и бинарник; рабочий архив не подменялся.
Первоначальный стенд с настоящим SQLITE_DENY теперь PASS:dm_start=-1,out1->1,in2->2,
две строки истории сохранены,allocations0->0. Оба контрольных лога прочитаны полностью.
Сборка Linux PASS; DM-профиль test_dm_load/dm/history/e2e/invite/media/worker7/7 PASS.
ASAN/UBSan/LSan тот же профиль7/7 PASS с detect_leaks=1/halt_on_error=1.
Отдельная копия /tmp/utun-dm-load-sanitizers:новая сборка core/libuasync/Opus
с согласованными -fsanitize=address,undefined,без смешивания обычных объектов.
dm_core.c,test_dm_load.c,tests/Makefile.am побайтно совпадают с рабочими.
Проверены все sanitizer-логи:нет отчётов ASAN/UBSan/LSan;test_dm_load allocations0->0.
Первый полный check.sh:202 passed,1 failed,0 skipped (203 total).
Отказ test_chat_join_e2e:happy A!=C,J не декодировал приглашение,потом A/C не нашли J;
degraded и invalid-key PASS. Отдельный диагностический повтор3/3 PASS.
Код теста создаёт link.txt до записи/закрытия;читатель ждёт только access(F_OK).
Это возможная гонка тестовой публикации,но конкретная причина исходного отказа
не доказана без размеров/результатов чтения. Отдельная задача в doc/tasks.md;
production JOIN и критерии/код теста не менялись,первый отказ сохранён.
Окончательный повтор полного check.sh:203 passed,0 failed,0 skipped;exit0,175с.
Интеграции proxy9/9,burst1/1,load8/8 PASS,134217728 проверенных байт.
В этом прогоне новый test_dm_load PASS;код после профильных проверок не менялся.
Повторный успех не закрывает отдельную задачу диагностики исходного отказа JOIN.
Логи исправления:
/tmp/dm-load-fix-build.log (окончательная сборка PASS)
/tmp/dm-load-fix-profile-results.log и /tmp/dm-load-fix-test_dm*.log
/tmp/dm-load-fix-controls-results.log и /tmp/dm-load-fix-control-*.log
/tmp/dm-load-fix-negative-build.log и /tmp/dm-load-fix-original-build.log
/tmp/dm-load-fix-sanitizers-build.log,/tmp/dm-load-fix-sanitizers-results.log,
/tmp/dm-load-fix-sanitizers-test_dm*.log,/tmp/dm-load-fix-final-verification.log
/tmp/dm-load-fix-full-check-first.log,/tmp/dm-load-fix-join-first.log,/tmp/dm-load-fix-join-repeat.log
/tmp/dm-load-fix-full-check.log,/tmp/dm-load-fix-netns.log,/tmp/dm-load-fix-repeat-netns.log
Полный набор запускается в отдельном IPv6 network namespace без изменения сети хоста.
Начальные ошибки подготовки sanitizer-копии (недостающие generated auxiliary files/
etcpmon header и ограничение LSan в sandbox) устранены до успешной сборки/прогона.
В первом native build не хватало openssl/evp.h в новом тесте;добавлен до успешной сборки.
GUI/Android/Windows/FreeBSD не запускались;регрессия с linker wrappers — Linux.
Дефект счётчиков исправлен;исходные частичные отметки аудита остальных DM owners сохраняются.
Коммит/push не выполнялись.
5. Исходный отказ mailbox/media — 26.10.2026: transient SQL становится постоянным отказом M
Прочитаны полностью dm_mailbox.c,dm_mailbox_media.c,dm_media.c и четыре заголовка,
связанные file_transfer/media_async контракты,lifecycle instance и штатный test_dm_media.
Уточнены uasync/ownership/lifecycle,семантика принятия PUT,транзакция регистрации media,
предусловия размеров control,ограничение рассылки до64 S на группу и SQL-helper ownership.
Четыре новых заголовка отмечаются[~]:чтение завершено,но полный аудит контрактов/отказов
и документация media API не завершены. Прежние core/history/dm_arch[~],crypto[V] сохраняются.
Дефект в dm_custody_store:результат SELECT COALESCE(sum(cipher_size),0) FROM dm_custody
при любом rc!=SQLITE_ROW превращается в used=UINT64_MAX. Следующая ветка used>quota
выдаёт DM_MEDIA_REJECT reason1,то есть утверждает реальное переполнение квоты.
Оригинальная причина SQL не логируется;WARN показывает фиктивный used18446744073709551615.
dm_mailbox_media_recv на S сохраняет released=1 и holder=0. dm_job_storage исключает
все прежние назначения через NOT IN(SELECT node_id FROM dm_media_holders WHERE id=?),
включая released. Поэтому повторные STORE на этом M для UUID больше не возникают.
Доказательство:/tmp/dm-quota-audit.c/.mk,временный вариант штатного test_dm_media.
Настоящие четыре utun/chat ядра A/B/S/M,разные БД,подписанные channel/member/admin-блоки,
реальные UDP-транспорты и file_transfer. Сначала штатная прямая доставка и прежние
проверки rollback/реальной квоты;затем B останавливается,A отправляет новое вложение.
Прикладной алгоритм,архивы,production-объекты и штатный тест не подменялись.
Linker-wrapper однократно возвращает SQLITE_IOERR только для used-space SELECT в БД M.
В обеих проверках настроено1024МиБ,текущая custody пуста;ciphertext запрашивает98385байт.
Контроль без отказа:назначение M->custody reserved->файл сохранён->STORED на S.
Через16 календарных секунд тестовых часов:ready1,released0,waiting0,holders1,used98385;
phase9 PASS,exit0,allocations0->0.
Fault:SQLITE_IOERR->quota refused usedUINT64_MAX requested98385 quota1073741824,
затем storage refused reason1 на S. Через16с:ready0,released1,waiting1,holders1,used0;
quota-запрос STORE выполнен только один раз (финальный диагностический SELECT в счётчик не включён).
phase4 ожидаемо FAIL,exit1,allocations0->0. После однократного отказа SQLite работает;
возобновления назначения нет. Persistent-исключение после restart следует из таблицы/SQL,
отдельный restart в этом стенде не выполнялся.
Логи:/tmp/dm-quota-audit-build.log,/tmp/dm-quota-audit-results.log,
/tmp/dm-quota-audit-control.log и /tmp/dm-quota-audit-fault.log.
Ключевые строки всех переходов сохранены в /tmp/dm-quota-audit-diagnostics.log.
В baseline protocol_checks ожидаемы отдельный SQL-trigger rollback и настоящий
quota-отказ2МиБ при квоте1МиБ:они происходят до включения fault и не являются новым дефектом.
Работающие transport/media callbacks и SQL-снимки однозначно показывают механизм;
production-диагностика должна дополнительно различать SQL-ошибку и настоящую квоту.
Простое исправление:проверять результат чтения занятого места отдельно,логировать
ошибки SQL с rc/причиной и возвращаться без REJECT. Сохранённый holder/stored0 на S
уже обеспечивает повтор STORE через существующий таймер. Реально недостаточная
квота сохраняет нынешний REJECT,выбор другого M и учёт всех назначений.
Также проверить bind/finalize,не выдавать operational-ошибку за quota refusal.
Постоянная регрессия:однократные SQL-отказы не исключают свободный M,последующий
STORE сохраняет ciphertext;реальная квота по-прежнему отклоняет запрос до загрузки.
Новый протокол/формат БД/специальный control для retry не требуется.
По AGENTS.md:10 аудит остановлен до исправления;открытая задача в doc/tasks.md.
В этом этапе изменены только комментарии/документы/отметки;исправление dm_conv_load
и новая постоянная регрессия из предыдущего этапа сохранены. Новые fullcheck/ASAN/UBSan/LSan,
GUI/Android/Windows/FreeBSD не запускались;прежние203/203 и sanitizer7/7 относятся
к предыдущему исправлению,а не к новому отказу custody. Коммит/push не выполнялись.
6. Исправление quota-admission — 26.10.2026
По запросу пользователя dm_custody_store проверяет чтение занятого места и finalize
до сравнения с квотой. Ошибка prepare уже возвращалась без ответа;теперь ошибка step,
неожиданный SQLITE_DONE и ошибка finalize также прекращают STORE без REJECT/регистрации.
Лог step/finalize содержит rc,SQL-причину и указание на сохранённый повтор STORE.
Прежнее назначение M остаётся на S;его штатный таймер повторяет STORE. Настоящий
недостаток квоты по-прежнему выдаёт reason1. У агрегатного SELECT нет bind-параметров.
Протокол,формат БД и алгоритм назначения S не менялись.
Постоянный test_dm_media расширен fault wrappers только для Linux:
- Отдельные prepare/step/finalize/неожиданный DONE проверяют один отказ за вызов,
отсутствие REJECT/STORED,отсутствие изменений SQL в S/M,пустые custody/tombstones,
autocommit. Достаточная квота настроена1024МиБ;отказ не заменяется успешной регистрацией.
- Настоящее превышение квоты требует ровно один REJECT без STORED/регистрации custody.
- Сетевой сценарий использует прежние4 ядра,подписанные роли,настоящие UDP/router/transfer.
После отключения B первые3 STORE получают разные SQL-ошибки на M:prepare,step,finalize.
Четвёртый STORE сохраняет файл на том же M;REJECT0,STORED1,одно назначение,released0.
Последующие штатные проверки перезапуска S/M при выключенном A,точного файла у B,
квитанций,удаления промежуточной копии/тела S,поздних STORE/STORED и TTL сохранены.
Исходные4 сбоя проверяют обработчик после router,сетевой retry идёт обычным транспортом.
Нативный и sanitizer-профили7/7 PASS:test_dm_load/dm/history/e2e/invite/media/worker.
В test_dm_media phase9 PASS,allocations0->0;в native/sanitizer одинаковая последовательность
hits3,STORE4,REJECT0,STORED1,holders1. ASAN/UBSan/LSan7/7 PASS,detect_leaks=1,
halt_on_error=1;ядро,libuasync и встроенный Opus собраны с одинаковыми sanitizer-флагами.
Использована отдельная копия /tmp/utun-dm-quota-sanitizers;обычные объекты не подменялись.
Побайтно сверены dm_media.c/test_dm_media.c/tests/Makefile.am и прежние dm_core.c/test_dm_load.c.
Контроли:
- Новый test_dm_media с dm_media.c из HEAD ожидаемо FAIL,exit1:step-отказ даёт ложный
REJECT,который ловит новая проверка. allocations0->0. Старый модуль собран отдельным
объектом и включён в отдельный бинарник;рабочий libutun.a не подменялся.
- Исходный /tmp/dm-quota-audit.c с одним SQLITE_IOERR и исправленным core:phase9 PASS,exit0.
- test_dm_media без DM_QUOTA_FAULT_INJECTION:штатный сценарий phase9 PASS,exit0.
Это Linux-компиляция без wrappers;runtime других ОС не проверялся.
Все3 контроля восстанавливают tracked allocations до0. Полные логи изменённого
media-теста/native/sanitizer/контролей прочитаны;ожидаемые ERROR относятся к инъекциям.
Артефакты:
/tmp/dm-quota-fix-build.log
/tmp/dm-quota-fix-profile-results.log,/tmp/dm-quota-fix-test_dm*.log
/tmp/dm-quota-fix-sanitizers-build.log,/tmp/dm-quota-fix-sanitizers-results.log,
/tmp/dm-quota-fix-sanitizers-test_dm*.log
/tmp/dm-quota-fix-controls-build.log,/tmp/dm-quota-fix-controls-results.log,
/tmp/dm-quota-fix-control-{test_dm_media_old,dm-quota-fixed-original,test_dm_media_no_injection}.log
/tmp/dm-quota-fix-final-verification.log
/tmp/dm-quota-fix-full-check.log,/tmp/dm-quota-fix-netns.log
Полный check.sh203/203 PASS,0 failed/0 skipped;proxy9/9,burst1/1,load8/8 PASS,
134217728 проверенных байт,exit0,176с. Этот прогон включает новую fault/retry-проверку media.
Набор выполнен в отдельном IPv6 network namespace;сеть хоста не менялась.
Первый сборочный запуск отдельного negative-control не включал test_clock.o в линковку;
временная make-цель исправлена до контрольных прогонов. Основная сборка/код от этого не менялись.
Документация обновлена,задача quota-admission закрыта. Остальные отметки аудита DM остаются
частичными;GUI/Android/Windows/FreeBSD не запускались. Коммит/push не выполнялись.
7. Commit/rebase/push по запросу пользователя — 26.10.2026
Все накопленные изменения DM сохранены в2ae1990d. Fetch обнаружил8 новых коммитов
origin/master поверх03b05094:изменения headless PCM,документации звонков и их проверок.
Rebase на03be46c3 завершён с единственным конфликтом doc/tasks.md:сохранены обе
полные группы задач. tests/Makefile.am объединился автоматически,цели test_dm_load,
quota wrappers test_dm_media и upstream test_call_headless_queue присутствуют.
DM .c/.h и обе новые/расширенные регрессии побайтно сохранены относительно исходного коммита.
После rebase коммит стал b6588d059455ed430c902a325af7d010f15f3475.
Нативная сборка PASS;новый полный check.sh204/204 PASS,0 failed/0 skipped,
proxy9/9,burst1/1,load8/8 PASS,134217728 байт,exit0,175с. Включён новый upstream
test_call_headless_queue (11/11) и все DM-регрессии. Сеть — отдельный IPv6 namespace.
Push origin master:03be46c3..b6588d05 PASS,без force. git ls-remote подтвердил hash,
HEAD==origin/master,рабочее дерево после push было чистым.
Логи:/tmp/dm-audit-rebase-build.log,/tmp/dm-audit-rebase-full-check.log,
/tmp/dm-audit-rebase-netns.log. Санитайзеры после rebase заново не запускались;
исходные DM-файлы по сравнению с прошедшим sanitizer-профилем не менялись.
8. Продолжение: UTF-8 имя повреждается при построении беседы
Продолжены сверка core/history,передачи поколения и путей из attachment_send,
участков headless/GUI/JNI вызывающих функций. Это чтение кода,не аппаратная проверка UI.
Уточнён dm_send_file:0 означает вызов submit,который вправе синхронно вызвать callback
ошибки;результат подготовки сообщается событием. Это исправление описания возврата.
Новый подтверждённый дефект:dm_conv.peer_name[64],snprintf в dm_start/dm_conv_load
режут имя по63байтам,без проверки границы UTF-8. json_escape передаёт получившиеся
байты в JSON без проверки/исправления. Корректное входное имя тем самым повреждается
в SQLite,а dm_list_conversations_json возвращает0 с некорректным UTF-8 JSON.
dm_start_req допускает до127байт имени;отсутствует предусловие ASCII/<=63байт.
Стенд:/tmp/dm-utf8-audit.c/.mk. Настоящие core/chat_service/SQLite/X25519,
отдельные ключи для трёх собеседников,новая БД/tmp;публичные dm_start/list API.
Валидные входы:ASCII63байта;31×«Я»=62байта;32×«Я»=64байта.
Контроли ASCII63/UTF8_62 дают корректный UTF-8 JSON. UTF8_64 сохраняется как63байта,
последний байт0xd0 без продолжения;start=0,list=0. Строгий Python decode JSON
даёт invalid continuation byte на позиции537;проверка exit2. SQL-readonly-снимок
подтверждает тот же обрыв в сохранённом peer_name,tail_hex=afd0afd0afd0afd0.
Tracked allocations0->0;собственно C-стенд exit0,отказ обнаруживает независимый валидатор.
Сам runtime-лог тоже содержит этот некорректный байт:валидатор читает диагностический
лог с replacement только для поиска ASCII-путей,но JSON и SQL BLOB проверяет строго.
Причина однозначна по точным входным/сохранённым длинам и байтам;это не ошибка JSON
парсера,шифрования или вывода терминала. Повторная загрузка имени через dm_conv_load
имеет то же побайтовое усечение;этот путь в новом стенде отдельно не запускался.
Простой план:определить единое ограничение имени и копировать только целые UTF-8
code points. Проверять входы и уже сохранённые некорректные строки с диагностикой;
увеличение массива само по себе не решает обрыв на новой границе.
Постоянные проверки:ASCII и2/3/4-байтовые символы до/на/за границей,SQL round-trip,
повторная загрузка и строгая декодируемость JSON. До выбора обработки уже повреждённых
имён (безопасное усечение/отказ) рабочая реализация не менялась.
Логи:/tmp/dm-utf8-audit-build.log,/tmp/dm-utf8-audit-runtime.log,
/tmp/dm-utf8-audit-validation.log;валидатор:/tmp/dm-utf8-audit-validate.py.
По AGENTS.md:10 дальнейший аудит остановлен,задача открыта в doc/tasks.md.
В продолжении только комментарии/документы,все текущие частичные отметки сохранены.
Предыдущий check204 проверял объединённые исправления,а не обработку нового имени;
новые sanitizer/GUI/Android/Windows/FreeBSD проверки не запускались.
9. Исправление UTF-8 имени собеседника
Предел имени явно задан DM_PEER_NAME_MAX=63 байта без NUL. Единый dm_copy_peer_name
сохраняет целые UTF-8 code points при dm_start, dm_conv_load, автосоздании во входящем
dm_accept_message и построении снимка бесед. Входящее имя из nodes сначала читается
в128байт, чтобы прежнее предварительное усечение в64байта не портило символ.
JSON нормализует как SQL peer_name,так и fallback из nodes при пустом имени.
Некорректная последовательность,overlong,surrogate,значение выше U+10FFFF или NUL
внутри SQL TEXT обрывают имя перед первым недопустимым code point. WARN содержит
conv_id,смещение и исходную длину;обычное усечение длинного имени отмечается DEBUG.
Чтение/снимок не исправляют SQL автоматически. Следующая штатная запись беседы
сохраняет уже нормализованное имя;формат БД/wire и счётчики сообщений не меняются.
Постоянный test_dm_names добавлен в штатный набор tests/Makefile.am:
68 случаев с реальными core/chat_service/SQLite и отдельными ключами собеседников.
ASCII0/1/62/63/64/127байт;границы2/3/4-байтовых символов до/на/за63байтами,
повторяющиеся символы,включая U+10FFFF;12 видов некорректных последовательностей
с пустым/непустым префиксом;SQL TEXT с NUL;кавычки/слеш/newline и кириллица.
Каждый случай проверяет точные SQL/JSON имена при создании,чтении повреждённого
сохранённого имени,load через чтение сообщений,load/save через dm_send и fallback.
total_changes и исходные SQL-байты подтверждают отсутствие скрытых записей при чтении.
Отдельно проверен restart с той же БД/идентичностью;allocations0->0.
Независимый строгий Python decode UTF-8 + json.loads проверил273 полных снимка,
9452 строки бесед и предел63байта на каждое имя;PASS в native и sanitizer сборках.
Контроль с прежним core:новая регрессия FAIL,exit1,на чтении длинного SQL-имени
после первых4 случаев (прежний JSON обходил предел);allocations0->0.
Исходный стенд раздела8 повторён на исправленном core:ASCII63/UTF8_62/UTF8_64 PASS,
32×«Я» сохраняются как62байта,строгий JSON всех3 снимков корректен,allocations0->0.
Исходные доказательства дефекта сохранены в прежних /tmp/dm-utf8-audit-*.log.
Нативная сборка PASS;DM-профиль8/8 PASS: names/load/crypto/history/e2e/invite/media/worker.
ASAN/UBSan/LSan8/8 PASS,detect_leaks=1,halt_on_error=1;отчётов о памяти/UB/утечках нет.
Изолированная копия /tmp/utun-dm-utf8-sanitizers синхронизирована с текущими исходниками,
включая upstream call_headless;ASAN/UBSan-ссылки проверены в core/libuasync/Opus.
Полный check.sh205/205 PASS,0 failed/0 skipped,proxy9/9,burst1/1,load8/8,
134217728байт,exit0,172с. Сеть — отдельный IPv6 namespace,сеть хоста не менялась.
Логи:/tmp/dm-utf8-fix-{build,profile-results,test_dm_names,negative-build,negative}.log,
/tmp/dm-utf8-fix-{original-build,original,original-validation,json-validation}.log,
/tmp/dm-utf8-fix-{sanitizers-build,sanitizers-results,sanitizers-test_dm*,full-check,netns}.log.
Задача UTF-8 закрыта;общий аудит DM остаётся частичным:crypto[V],остальные6headers
и dm_arch[~]. Платформенные GUI/Android/Windows/FreeBSD проверки не выполнялись.
Финальная сверка перед отправкой:fetch принёс4 Android mute-коммита до fa76447f.
Rebase прошёл без конфликтов;DM исходники/headers,Makefile и регрессия побайтно
сохранены. Коммиты исправления/отчёта после rebase:d09ed031/87d24b72.
Повторная native сборка PASS;check.sh205/205 PASS,0 failed/0 skipped,proxy9/9,
burst1/1,load8/8 PASS,134217728байт,exit0,175с. Строгие273 JSON/9452строки снова PASS.
Логи:/tmp/dm-utf8-fix-rebase-{build,full-check,json-validation,netns}.log.
Санитайзеры после rebase не повторялись:DM runtime не менялся,общий call_audio.h
в upstream изменяет только комментарий;новые платформенные Android callers этим
DM-профилем не проверяются. Upstream задачи/отчёты Android mute сохранены целиком.
10. Продолжение mailbox: SQL-ошибка оставляет завершённое TTL-назначение активным
База продолжения:3dc5eb502db6964fc6467385cc65db9d1df83c44,рабочее дерево было чистым.
Повторно сверены dm_mailbox.c/.h,dm_mailbox_media.c/.h и относящиеся к квитанциям,
контролю и TTL функции dm_media. Приём PUT проверяет точное тело,роль S и общую
группу;квитанция текста и удаление pending объединены транзакцией. Это чтение
соответствующих путей,не полная проверка всех SQL/allocator/платформенных отказов.
Подтверждён новый дефект:dm_mailbox_media_recv в EXPIRED сначала делает
UPDATE dm_media_holders SET released=1,затем отдельным statement обновляет job:
holder=0,stored=0,expired=1,retry_at=0. При отказе второго statement первое изменение
остаётся в БД. Таймер при недоступном B видит прежние holder и stored=1,поэтому
не выбирает нового M,не повторяет STORE и не сверяет expires с текущим временем.
На M custody уже удалена;dm_custody_done хранит tombstone,но таймер эту таблицу
не сканирует и не повторяет уведомление EXPIRED сам по себе.
Стенд:/tmp/dm-expiry-audit.c/.mk — отдельный вариант штатного test_dm_media.
Настоящие4 ядра A/B/S/M с подписанными мемберскими/административными блоками,
разными SQLite,реальными UDP/BGP/file_transfer и прежними проверками доставки.
Штатные quota-fault проверки идут в обоих прогонах одинаково;новая инъекция только
на S и только на UPDATE завершения EXPIRED,однократно SQLITE_IOERR в sqlite3_step.
Рабочие C,штатные тесты,объекты и libutun не подменялись;данные только в/tmp.
Оба прогона сначала проходят прямую доставку,offline custody и восстановление
S/M с выключенным A,побайтную доставку B и удаление промежуточной копии.
Затем B снова выключен;для третьего файла настроен TTL1с на M. M успевает принять
98385байт ciphertext,подтвердить хранение,по TTL удалить custody и послать EXPIRED.
Контроль без новой инъекции:phase9 PASS,exit0,allocations0->0. Для seq3 на S
holder0,stored0,expired1;назначение released1. На M нет custody,есть expired tombstone.
Fault:UPDATE завершается rc10,hit1,ошибка логируется вместе с точным SQL. После
ещё16 календарных секунд тестовых часов и нескольких успешных повторов PUT:
на S holder723190674248656275,stored1,expired0,expires1793032708,receiptNULL,body сохранён;
для того же UUID E20AC64A2660882CE73D68D48E2AFFA0 назначение этого M released1.
На M custody0,expired tombstone1. Phase8 FAIL,exit1,allocations0->0.
В runtime released=2 считает также прежнее успешно доставленное назначение;
независимый readonly SQL JOIN отдельно подтвердил released1 для проверяемого UUID.
Остановка/освобождение owners штатные,не маскируют сохранённое расхождение.
Окно отсутствия восстановления16с проверено исполнением;отсутствие пути восстановления
при продолжающемся offline B следует также из ветки таймера. Restart после этой
инъекции,возврат B и дополнительный storage не запускались;потеря до TTL не заявляется.
Это нарушение согласованности durable состояния после SQL-отказа,а не ошибка самого
удаления по TTL:копия M удалена в предусмотренный срок.
Направление исправления:идемпотентный восстанавливаемый переход EXPIRED с согласованным
состоянием job/holder;подтверждённое назначение проверять таймером по сохранённому expires.
Одного BEGIN/COMMIT вокруг двух UPDATE недостаточно:при rollback событие уже потреблено,
а M не повторит EXPIRED после удаления custody. Нужны отказные проверки обоих statement,
commit и восстановления после restart;поздний STORED не должен отменить завершение.
Сходная последовательность REJECT/DELETE_ACK замечена при чтении,но отдельный runtime
её отказов в этом продолжении не выполнен. Конкретная реализация пока не менялась.
Сборка отдельного стенда PASS. Логи:/tmp/dm-expiry-audit-{build,control,fault,validation}.log;
полные журналы сохранены,DM-выборки без транспортного шума — *-control-dm.log/*-fault-dm.log.
Прежний check205/санитайзеры относятся к исправлению UTF-8 и не покрывали этот отказ.
Новые fullcheck/sanitizer/GUI/Android/Windows/FreeBSD проверки не выполнялись.
По AGENTS.md:10 дальнейший аудит остановлен;задача открыта в doc/tasks.md.
В продолжении только комментарий контракта и отчёт/задача;частичные отметки сохранены.
11. Исправление восстанавливаемого завершения TTL
На S добавлен единый dm_job_release для EXPIRED/REJECT:BEGIN IMMEDIATE,освобождение
назначения и сброс текущего holder/stored,COMMIT. Prepare/bind/step/finalize обоих
statement и COMMIT проверяются;при ошибке ROLLBACK,без сообщения об успешном переходе.
Лог ошибки содержит M,rc/finalize,точный SQL и причину;успех — M,причину expired/rejected
и отдельные счётчики изменения назначения/job. Идемпотентный повтор не пишет released
заново;старый M не сбрасывает нового holder. WHERE receipt IS NULL сохраняет приоритет
подписанной квитанции B. DELETE_ACK остаётся отдельным одношаговым освобождением.
Таймер S независимо проверяет сохранённый expires подтверждённого stored-назначения.
При истечении выполняет тот же переход и перечитывает job после commit;ошибка остаётся
на следующем retry_at. Это работает при offline B,потерянном EXPIRED и после restart.
Для ещё не подтверждённого хранения STORE продолжает повторяться;M отвечает из
custody/tombstone. Поэтому для отсутствующего STORED новый механизм не требует
выдуманного срока хранения. Формат таблиц/wire,подписи и TTL на M не менялись.
Лог удаления тела S уточнён:освобождены все назначения. Завершение TTL не утверждает,
что worker M уже закончил физическое удаление в тот же момент;его владелец и повторы
сохраняются на M. Неистёкшие назначения продолжают удаляться до DELETE_ACK.
Штатный Linux test_dm_media расширен,без отдельной подмены production:
18 component-сценариев на настоящем job S:EXPIRED и REJECT × prepare/bind/step/finalize
обоих UPDATE и COMMIT. После каждого отказа сравниваются все6 полей назначения/job
(holder,stored,expired,expires,retry_at,released),проверяется autocommit;нормальный повтор
завершает переход,дубликат и поздний STORED не меняют результат и не создают receipt.
Фикстура возвращается синхронно до следующего uasync callback;это component-проверка
SQL/control,не утверждение о реальном удалении M на каждом из18 искусственных событий.
Сетевой TTL-сценарий использует настоящие4 ядра A/B/S/M и прежний полный путь
offline-файла с restart S/M при выключенном A,точным файлом на B и удалением копий.
Затем B отключён,на M TTL1с. EXPIRED от M намеренно теряется,SQL-сброс job на S
блокируется до удаления custody M. Проверены сохранённый holder/stored и released0
после rollback;S перезапускается. Без получения EXPIRED таймер S восстанавливает
holder0,stored0,expired1,released1. Поздний STORED с будущим expires и STORE не
возрождают назначение/файл и не выдают ложную квитанцию доставки.
После restart поздний component-control ждёт реального восстановления ETCP-линка.
Native и ASAN:18 checks,один заблокированный сброс,два потерянных EXPIRED (включая
ответ на поздний STORE),restart1,phase9 PASS,allocations0->0.
Независимое readonly чтение SQLite по точному UUID подтвердило на M custody0,
expired tombstone1;на S holder0/stored0/expired1,receiptNULL,body сохранён и released1.
Прежний job seq2 сохранил квитанцию и удалённое тело. Проверено и в sanitizer-копии.
Прежний координатор в отдельном /tmp/dm-expiry-old-coordinator.o/бинарнике ожидаемо FAIL
новой регрессии на bind-отказе первого UPDATE:частичный переход,phase4,exit1,
allocations0->0. Рабочий libutun не подменялся. Исходный one-shot IOERR-стенд раздела10
повторён на исправлении:matching SQL дополнен receipt IS NULL,hit1,затем успешный
переход,phase9 PASS,exit0,allocations0->0;исходные доказательства дефекта сохранены.
Нативная сборка PASS;DM-профиль names/load/crypto/history/e2e/invite/media/worker8/8 PASS.
ASAN/UBSan/LSan8/8 PASS,detect_leaks=1,halt_on_error=1;отчётов памяти/UB/утечек нет.
Изолированная /tmp/utun-dm-expiry-sanitizers побайтно совпадает с проверяемыми
dm_mailbox_media.c/.h,tests/Makefile.am/test_dm_media.c;core/libuasync/Opus содержат
ASAN/UBSan-инструментацию. В media-логе при restart один packet too small for init
size68;прогон восстановил линк и завершился,это не отчёт санитайзера.
Первую сборку запустил до завершения изменения linker flags,она отказала на
__real_sqlite3_bind_blob. После окончания правок повторная полная сборка и отдельная
сборка media PASS;проверки выполнялись на согласованных окончательных объектах.
Полный check.sh205/205 PASS,0 failed/0 skipped;proxy9/9,burst1/1,load8/8 PASS,
134217728байт,exit0,173с. Сеть — отдельный IPv6 namespace,настройки хоста не менялись.
Логи:/tmp/dm-expiry-fix-{final-build,media-build,profile-results,test_dm*,validation}.log,
/tmp/dm-expiry-fix-{negative-build,negative,original-build,original}.log,
/tmp/dm-expiry-fix-{sanitizers-build,sanitizers-results,sanitizers-test_dm*,full-check,netns}.log.
Задача завершения TTL закрыта. Общий аудит DM частичный;прежние отметки сохраняются.
GUI/Android/Windows/FreeBSD не проверялись;DELETE_ACK fault matrix отдельно не добавлялась.
12. Завершение аудита контрактов DM — 27.10.2026
База заключительного этапа: /home/user/utun4, master, исходный HEAD07f63c3047b4a9bef6c568ca590d3fefde95816f.
Полностью сверены все семь заголовков src/dm/ с реализациями core, crypto, history,
mailbox, mailbox_media и media; doc/dm_arch.md, doc/pm.txt и doc/pm_invites.md.
Это завершение проверки описаний/API, не утверждение об отсутствии всех возможных
ошибок программы. Новых подтверждённых дефектов выполнения в этом этапе нет.
В заголовках уточнены:
- Заимствование аргументов, один освобождаемый блок запросов UI, жизнь instance,
поток uasync и запрет доступа workers к SQLite/сети.
- Проверка тела/подписи отдельно от области router/ролей, точные повторы и конфликты,
границы транзакций при send/accept, подпись квитанции после commit входящего сообщения.
- Независимые квитанции сообщения и файла; STORED не удаляет исходящий outbox.
- Форматы DM/mailbox/media control, native-endian чисел и big-endian attachment_info,
лимиты send data с descriptor и размеры выходных буферов квитанций.
- Поколения/hidden, сохранение ключей и seq, отпечатки удалённых тел, отмена старых
invite/transfer и отбрасывание завершения worker старого поколения.
- Роли S/M, выбор нового storage без прежних назначений, фиксация назначения до STORE,
local S/M control и отличие передачи пакета от durable приёма.
- File holds, манифест путей, busy=-1 уборщика, повтор удаления и fsync каталога;
пользовательский исходник не передаётся уборке без явного remove_source.
- Запуск mailbox/core/media и остановка quiesce -> media_async drain -> destroy
с живыми SQLite/группами, сохранение данных между stop/restart.
dm_arch согласован с порциями таймеров32/8, обычным retry5с и отдельными retry3600с/30с,
сохранённым retry_at текстовых очередей и сбросом медиа при restart. Добавлены wire
таблица, порядок владельцев и восстановление оригинала после EXPIRED: срок копии M
не запрещает B получить исходный файл A; поздний EXPIRED не портит готовый файл.
pm.txt дополнен привязкой требований к текущим owners/ролям/настройкам, dm_arch
больше не называет этот аудит частичным. Исторические результаты сборок помечены датой.
Сверены относящиеся к контрактам callers: chat_service_start/stop, attachment_send,
file_transfer lookup/cancel, Qt DmInviteDialog/MainWindow/MessageList и bridge,
Android JNI/ViewModel/PmInviteDialog, headless DM handlers, регистрация JOIN-ключей
и планирование member_sync после JOIN_READY/принятия участника.
Клиентский код просмотрен для описанных DM-событий/выбора адресата/подготовки файла;
полный аудит Qt/Android и остальных общих модулей этим не заявляется.
pm_invites описывает владение запросом, поколение до регистрации и отсутствие
отката открытой беседы при последующей ошибке; отмена PM не отзывает JOIN-ключ.
Проверки текущего этапа:
- make -C src -j4 и сборка всех девяти существующих DM-целей: PASS.
- Native test_dm/history/names/load/e2e/invite/media/worker/media_cleanup:9/9 PASS.
test_dm_load:79 проверок; names:68 случаев и273 снимка, независимый Python JSON
parser прочитал каждый полный снимок. Worker:25 запусков,50 heartbeat,max_gap11.3мс.
Media:phase12,6266 heartbeat,allocations0->0; проверены11 retirement SQL faults,
18 release faults, потерянные control/restart и возврат точного оригинала после TTL.
- Linux cleanup component:13 SQL faults для scan/ACK/body, повтор после restart,
несколько M, потерянный/повторный/чужой ACK и поздний STORED; exit0.
Сеть перехвачена, receipt задан фикстурой; криптографию и физическое удаление M
проверяет отдельный test_dm_media. Одношаговый autocommit может уже завершиться
до искусственного finalize failure; тест проверяет это состояние и идемпотентный повтор.
- Каждый из семи заголовков: C99 и C++20 с -Wall -Wextra -Werror,14/14 PASS.
Для private/media C API C++ использует extern "C", как существующие клиенты;
include path lib/ соответствует проекту и предоставляет sqlite3.h.
- Все C-файлы DM побайтно совпадают с начальным снимком; C-токены всех заголовков
совпадают после исключения комментариев/пробелов. Алгоритмы, API/ABI, wire и SQL
этим этапом не менялись. git diff --check PASS; новые тесты не добавлялись.
Логи и снимки: /tmp/dm-contract-finish-before/sha256.json,
/tmp/dm-contract-finish-{build,final-core-build,final-tests-build,headers,validation}.log,
/tmp/dm-contract-finish-native-{results,test_dm*}.log; исходные тела/JSON — в test artifacts.
Сетевой профиль выполнен вне sandbox. Первый sandbox-запуск cleanup вернул exit0,
но uasync сообщил Operation not permitted при создании служебного fd; окончательный
повтор вне sandbox прошёл без этих сообщений. Сетевые настройки хоста не менялись.
Новые full check.sh, ASAN/UBSan/LSan, сборки/интерактив Qt/Android/Windows/FreeBSD
не выполнялись; прежние результаты не приписываются заключительному прогону.
Изменения radio и doc/tasks.md сохранены отдельно от DM-коммитов; push не выполнялся.
13. Rebase перед push — 27.10.2026
После отказа push (origin содержал новые коммиты) пользователь запросил rebase.
Получен origin/master492f6adc; все шесть DM-коммитов перенесены на эту базу.
Единственный конфликт — dm_messages_req в dm_core.h: сохранены generation из origin
и проверенные комментарии. Остальные пять патчей перенесены без изменения по range-diff.
Старые хеши и результаты раздела12 относятся к состоянию до переноса.
В origin появился generation в запросе истории и CHAT_EVT_DM_MESSAGES:
[conv_len:1][conv][generation:8][JSON]. Сверены полный trampoline, проверка current,
новая регрессия test_dm_history и получение снимка Qt/Android. dm_arch уточнён под
этот формат. Все C-файлы DM побайтно совпадают с новым origin; токены семи заголовков
совпадают после исключения комментариев/пробелов. C99/C++20:14/14 PASS.
Перед rebase сохранены21 незакоммиченных radio-файл, включая untracked, в stash
и отдельной резервной копии с SHA256/режимами. После переноса восстановлены все21:
18 файлов побайтно, doc/tasks.md/tests/Makefile.am/tools/chatgui/CMakeLists.txt
слиты с origin; списки добавленных/удалённых строк radio точно совпали с сохранёнными.
Повторная проверка после сборки также PASS; radio не включается в DM-коммиты.
Свежий ./build.sh --no-gui -j4:PASS. После пересборки native DM9/9 PASS,
включая скрытые/устаревшие запросы истории и точное поколение опубликованного снимка.
Дополнительно radio/radio_mesh/radio_headless/radio_headless_queue4/4 PASS.
Это профиль13 бинарников, не полный check.sh; новые GUI/Android/sanitizer прогоны
не выполнялись. Радио EOS C++-профиль не повторялся, его исходники сохранены побайтно.
Логи:/tmp/utun4-dm-rebase-{build,results,radio-preservation,test_*}.log.
Резервная копия указана в /tmp/utun4-dm-rebase-backup-path.txt.