diff --git a/doc/chat_audit_findings.txt b/doc/chat_audit_findings.txt index 189dbf79..2351a74f 100644 --- a/doc/chat_audit_findings.txt +++ b/doc/chat_audit_findings.txt @@ -909,3 +909,39 @@ utun_node,редактирование/удаление/события/отве создания запроса,файловых completion,удаления и восстановления;отметки[~] сохранены. По новой инструкции пользователя дефекты исправляются и проверяются по мере аудита, с последующим обновлением текстов,без остановки на обнаружении дефекта. + +Исходящий REQUEST — 27.10.2026, runtime d1bfd1d1 + +Подтверждён пропуск вставки attachment_key_requests при SQLITE_DONE/changes0: +RAISE(IGNORE) оставляет outbox REQUEST без запроса,после чего прежний таймер +сообщает об успехе. Новый постоянный тест до runtime-правки завершился ожидаемым +assert:reader в событии показал request0/state-1/command1/autocommit1. +Полный короткий лог:/tmp/attachment-outgoing-negative.log,исходная libutun не менялась. + +ak_request_file теперь владеет BEGIN IMMEDIATE/COMMIT,под блокировкой перечитывает +downloaded/wanted/ready/decrypting/error/key и отсутствие прежнего запроса. +Проверяет все prepare/bind/step/changes1/finalize запроса и общей ak_queue,затем +commit;только после него идут событие и отправка. Общий rollback сохраняет прежние +строки. Сам tick откладывает файлы и outbox при внешней транзакции без её изменения. +Если rollback потребовал quiesce/stop,tick больше не вызывает pump и не ставит таймер. +Чтение выбранного файла проверяет размеры identity/key,указатели,пути и finalize. +Wire/схема/API не менялись. Ошибки содержат этап/код/канал/автора,успех — committed. + +test_attachment_requests дополнен52 SQL-отказами с успешным повтором и дубликатом: +пустая/существующая очередь,BEGIN/COMMIT,pending/eligibility read,insert request, +outbox,каждый prepare/bind/step/finalize и число изменённых строк. +Дополнительно ABORT/IGNORE запроса/INSERT/UPDATE outbox,настоящий commit veto, +сохранность внешней транзакции до создания и при уже готовой к отправке очереди, +rollback fault с проверкой stop/unbind/отсутствия таймера и откатом при destroy. +Readonly reader проверяет обе строки перед событием и REQUEST,соседний ключ не меняется. +Нет маршрута/backpressure/send fault→настоящий restart→запрос,очередь и дата сохранены; +init сбрасывает retry_at,следующий проход повторяет REQUEST без нового события/запроса. +Расширенный тест PASS,баланс выделений восстановлен:/tmp/attachment-outgoing-unit.log. +Первичный native-профиль requests/decisions/results/chat_attachment/encrypted_attachment5/5 +PASS:/tmp/attachment-outgoing-profile.log и /tmp/attachment-outgoing-profile-logs. +Санитайзеры и общая итоговая проверка продолжаются;полный аудит attachment остаётся[~]. + +Перед этой правкой upstream4d87cab2 объединён merge8a0654e5:изменения Android-жестов +и документация DM сохранены без конфликтов;native runtime в upstream не менялся. +После merge Android assembleDebug PASS:/tmp/attachment-request-fix-merge-android-build.log. +Merge и проверенный REQUEST fix отправлены в origin обычным push8a0654e5. diff --git a/doc/encrypted_attachments.md b/doc/encrypted_attachments.md index 116b289a..4704c757 100644 --- a/doc/encrypted_attachments.md +++ b/doc/encrypted_attachments.md @@ -110,6 +110,15 @@ instance того же процесса остаются защищены общ создаёт; ручная пользовательская загрузка на таком узле запрашивает доступ. Сетевая классификация `peers.storage` для этого не используется. +Таймер создаёт исходящий REQUEST собственной BEGIN IMMEDIATE/COMMIT: повторно +проверяет, что файл скачан и нужен, ключ отсутствует и прежнего запроса нет; +затем сохраняет ровно один исходящий запрос и команду outbox. Все SQL-этапы, +число строк и commit проверяются. Уведомление и отправка следуют после commit. +Отказ сохраняет прежние строки, следующий проход повторяет подготовку. +При внешней транзакции весь проход файлов и отправки откладывается, не меняя её. +Если rollback не закрыл собственную транзакцию, таймер не запускается повторно: +модуль снимается и владелец останавливает ядро после poll. + В шапке desktop-клиента кнопка «Запросы расшифровки» видна при наличии запросов без решения и показывает их число. Список содержит участника, вложение, дату и отдельные кнопки выдачи/отказа. @@ -209,6 +218,11 @@ backpressure и повтором через 5 секунд; restart продол внешняя транзакция, неверные состояние/направление/идентичность/флаги/мембер, потеря ACK из-за маршрута/backpressure/send failure и повтор с прежней датой, отказ rollback с остановкой и закрытием БД владельцем. +Тот же тест проверяет52 SQL-отказа создания исходящего REQUEST с пустой/существующей +outbox, ABORT/IGNORE, commit veto, внешнюю транзакцию до создания и до отправки, +отсутствие повторного события/запроса, rollback fault без повторного таймера. +Readonly reader видит обе строки до события и перед REQUEST; при отсутствии маршрута, +backpressure и ошибке send настоящий restart сохраняет очередь и исходную дату запроса. `test_attachment_decisions` проверяет116 SQL-отказов/повторов выдачи и отказа, с пустой и существующей очередью: чтение подписанного мембера, допуск автора, запись состояния, diff --git a/tests/test_attachment_requests.c b/tests/test_attachment_requests.c index 17ca97c7..b4425bd5 100644 --- a/tests/test_attachment_requests.c +++ b/tests/test_attachment_requests.c @@ -482,11 +482,36 @@ static void test_outgoing_rollback_failure(void) { DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "PASS outgoing requests: rollback failure stops core without event/send or rearming timer"); } +static void test_outgoing_restart(void) { + struct fixture f; setup(&f); + for (int unavailable = 0; unavailable < 3; unavailable++) { + outgoing_reset(&f, 0); + if (!unavailable) f.route_up = 0; + else if (unavailable == 1) f.room = 0; + else f.send_rc = -1; + outgoing_tick(&f, 1, unavailable == 2); + struct snapshot saved = snapshot(&f); + chat_event_set_handler(f.inst, NULL); active = NULL; utun_instance_destroy(f.inst); + char config[1024]; + snprintf(config, sizeof(config), "[global]\ndb_path=%s\n[ntp]\nenabled=no\n" + "[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n", f.directory); + f.inst = utun_instance_create_from_str(f.ua, config); + assert(f.inst && !utun_core_start(f.inst) && !chat_service_start(f.inst)); + f.route.inst = f.inst; f.route_up = f.room = 1; f.send_rc = 0; active = &f; + chat_event_set_handler(f.inst, event); + struct snapshot restored = snapshot(&f); + assert(restored.exists && restored.incoming == 0 && restored.state == 0 && restored.command == 1 && restored.retry == 0); + assert(restored.created == saved.created); outgoing_tick(&f, 0, 1); + } + cleanup(&f); + DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "PASS outgoing requests: no route/backpressure/send error preserve queue and date across real instance restart"); +} + int main(int argc, char** argv) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_category_level(DEBUG_CATEGORY_MEDIA, DEBUG_LEVEL_INFO); utun_instance_set_tun_init_enabled(0); size_t baseline = u_get_allocated_count(); if (argc != 2 || strcmp(argv[1], "--outgoing")) { test_faults(); test_controls(); test_ack_retry(); test_rollback_failure(); } - test_outgoing_controls(); test_outgoing_faults(); test_outgoing_rollback_failure(); + test_outgoing_controls(); test_outgoing_faults(); test_outgoing_restart(); test_outgoing_rollback_failure(); assert(!fault && !fail_rollback && u_get_allocated_count() == baseline); DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "PASS attachment requests: reader before ACK/events, stable duplicate date, neighboring key and allocations preserved"); return 0;