DM — аудит документации и контрактов, 26.10.2026 База: /home/user/utun2, HEAD 03b050949cf36cc4868324be94a54eaddfdd494c. Задание: /home/user/utun-doc-audit-checklist.txt, раздел src/dm/ и связанные документы. 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 отдельно не добавлялась.