From dda4a780e3ae91abea80f0aace5e6038fca3bbc4 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 26 Oct 2026 17:42:45 +0100 Subject: [PATCH] Document unreconciled DM media expiry after SQL failure --- doc/dm_audit_findings.txt | 60 +++++++++++++++++++++++++++++++++++++++ doc/tasks.md | 14 +++++++++ src/dm/dm_mailbox_media.h | 4 ++- 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/doc/dm_audit_findings.txt b/doc/dm_audit_findings.txt index 286865cb..5880f5b1 100644 --- a/doc/dm_audit_findings.txt +++ b/doc/dm_audit_findings.txt @@ -380,3 +380,63 @@ burst1/1,load8/8 PASS,134217728байт,exit0,175с. Строгие273 JSON/9452 Санитайзеры после 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. +В продолжении только комментарий контракта и отчёт/задача;частичные отметки сохранены. diff --git a/doc/tasks.md b/doc/tasks.md index be363c4d..d646a1bd 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -4,6 +4,20 @@ ## Текущие задачи +[ ] **DM media: восстанавливать завершение TTL после SQL-отказа при EXPIRED** — подтверждено 26.10.2026. + На S обработчик сначала сохраняет released=1, затем отдельно сбрасывает holder/stored и ставит expired=1. + Однократный SQLITE_IOERR второго UPDATE оставляет stored=1,holder=M,expired=0 при уже удалённом файле M. + Настоящие A/B/S/M, UDP/file_transfer и TTL1с:контроль phase9 PASS;отказ phase8 FAIL после16с повторов, + allocations0->0 в обоих прогонах. Независимое readonly чтение обеих БД подтвердило точный UUID/состояния. + Существующий таймер при offline B не пересматривает stored по expires и не повторяет STORE; + M уже удалил custody и сохранил tombstone, поэтому повтор EXPIRED от его таймера не возникает. + Нужен идемпотентный восстанавливаемый переход и сверка подтверждённых назначений по сохранённому expires; + одной транзакции недостаточно, если EXPIRED уже потреблён и последующее событие не придёт. + Добавить отказы обоих UPDATE/commit, повтор/restart, поздний STORED и корректное удаление без ложных квитанций. + Стенд /tmp/dm-expiry-audit.c/.mk;логи /tmp/dm-expiry-audit-{build,control,fault,validation}.log. + Подробности: [dm_audit_findings.txt](dm_audit_findings.txt),раздел10. По AGENTS.md:10 аудит остановлен; + runtime и штатные тесты не менялись, остальные отказные ветки REJECT/DELETE_ACK ещё не проверены отдельно. + [+] **DM: сохранять границы UTF-8 при усечении имени собеседника** — исправлено 26.10.2026. Единый предел DM_PEER_NAME_MAX=63 байта; создание, загрузка, входящее автосоздание и JSON копируют только целые code points. Повреждённый суффикс отбрасывается с WARN и точным смещением. diff --git a/src/dm/dm_mailbox_media.h b/src/dm/dm_mailbox_media.h index b97ba5de..bd0f5e32 100644 --- a/src/dm/dm_mailbox_media.h +++ b/src/dm/dm_mailbox_media.h @@ -24,7 +24,9 @@ int dm_mailbox_media_register(struct dm_mb_media* media, uint64_t group, uint64_ /* После проверки router-подписи и размера команды в dm_media: group/source — доверенные поля router. * body заимствован на время вызова; RECEIPT=152, STORED=24, DELETE_ACK/EXPIRED=16, REJECT=17 байт. * STORED/REJECT/ACK относятся к сохранённому назначению M; RECEIPT проверяется отдельно подписью B. - * SQL-ошибки оставляют задачу для повторов; отдельные обновления здесь не объединены в транзакцию. */ + * SQL-ошибки логируются; отдельные обновления здесь не объединены в транзакцию. + * Известный дефект: отказ сброса job после EXPIRED оставляет stored/holder при released=1; + * пока B offline, таймер не восстанавливает переход. См. doc/dm_audit_findings.txt, раздел10. */ void dm_mailbox_media_recv(struct dm_mb_media* media, uint64_t group, uint64_t source, uint8_t cmd, const uint8_t* body, size_t len); /* Проверить 152-байтовую квитанцию и попытаться отправить доступным S общей группы.