Files
utun2/doc/tasks.md
T

581 lines
70 KiB
Markdown
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.
# Задачи по проекту
Сюда пишется список задач с короткой аннотацией. Если задача большая и имеет ТЗ - то ТЗ оформляется отдельным файлом, а сюда помещается аннотация и ссылка на ТЗ.
## Текущие задачи
[+] **Live: согласовать выдачу cursor с проверкой revision/KEY в media store** — 14.10.2026:
Каждый cursor хранит revision последнего выданного полного видео и пропускает новую только с полным KEY.
Неполный кадр прежней revision удаляется; аудио и чтение старой истории другими readers сохраняются.
После GAP, нарушения последовательности фрагментов и вытеснения reader начинает с полного KEY;
зависимые кадры и частичный KEY не выдаются. Готовый KEY не ждёт таймера.
Регрессии включены в обычный test_stream_media; отдельный запуск: `tests/test_stream_media --revision-transition`.
Неиспользуемый stream_media_receiver удалён; сборка, FEC, конфликты формата и восстановление проверяются на store/cursor.
Desktop/Android используют общий stream_media_open с проверкой выбранной эпохи и откатом нового owner при ошибке.
Дополнительный reader-объект не нужен: владение и курсор уже находятся в STREAM_HANDLE.
[+] **Live: оценить оставшиеся оптимизации архитектурного плана** — 14.10.2026:
stream_member_supernode читает adm_tags конкретного node_id при deleted=0, без списка канала, сортировки и снимков узлов.
Синтетический замер SQLite в памяти: проверка роли 1024 участников — около 540 мс прежним способом против 2,9 мс рабочим API.
test_stream_service проверяет подписанные выдачу/отзыв роли, изоляцию каналов, absent/deleted и независимость от чужого snapshot.
Полная проверка корректного профиля stream_member_load занимает около 0,30 мс на этом стенде; проверки подписей сохранены.
Кэш авторизации не добавлен: он требует учёта участников, подписантов и ключей канала, одной версии профиля недостаточно.
Relay/network оставлены вместе: общий транспорт, учёт активных выдач и срок жизни подписки уже имеют единое владение.
Механический перенос потребовал бы дополнительных внутренних интерфейсов без упрощения жизненного цикла.
[+] **Live: прямое превью Android без собственного decoder** — 14.10.2026:
Камера одновременно выводит в encoder Surface и TextureView; собственное превью больше не открывает
stream viewer/cursor, MediaCodec decoder, AudioTrack и адаптивные часы воспроизведения.
Attach/detach меняет только camera session, сохраняя encoder, audio, revision и origin;
ON_STOP снимает preview output сразу, без ожидания Compose recomposition. Отказ preview сохраняет encoder-only эфир.
Surface/texture удерживаются UI, ожидающими командами и camera sessions до окончания использования;
CameraDevice.onClosed освобождает также sessions, для которых Android не доставил отдельный onClosed.
TextureView сам учитывает sensor orientation; дополнительный UI rotation компенсирует лишь поворот дисплея.
Повторный поворот сенсора убран, DisplayListener учитывает также 180° без изменения размеров окна.
Диагностика показывает native transform, sensor/display/UI rotation, first frame, FPS, возраст кадров и освобождение outputs.
REALTIME timestamp сравнивается с elapsedRealtimeNanos; UNKNOWN не выдаёт выдуманный абсолютный возраст.
На SM проверены AV1, задняя/передняя камеры, ручной поворот, фон/возврат, закрытие/открытие preview,
полная остановка и повторный запуск. Нет собственного decoder; типичный возраст кадров около 130ms
вместо накопленных 6s; перед stop median=133.7ms, p95=175.7ms по 297 диагностическим samples.
Stop освободил codec и все sessions, camera HandlerThread завершён; после проверки восстановлены AV1/main/0°.
151 тест ядра и Kotlin unit tests прошли, в том числе 6 проверок preview; APK установлен на SM.
Архитектура: `doc/stream.txt`, `doc/service_lifecycle.md`.
Логи: `/tmp/direct-preview-{full-check,android-orientation-build,sm-live}.log`.
[+] **Live: независимая подача decoder и устранение вечного ожидания AV1 на SM** — 14.10.2026:
Причина: Android подавал максимум три кадра и читал видео только на 80ms вперёд от остановившегося clock;
c2.android.av1-dav1d.decoder требовал заполнения своего конвейера. KEY уже был принят, но UI продолжал ждать KEY.
Общий cursor теперь запрашивает следующий видеокадр независимо от PTS показа; audio сохраняет временной lookahead.
Bridges готовят один ответ на запрос, получение ответа/AVAILABLE не создают скрытого prefetch.
Android StreamDecodePump держит один input и один output, обслуживает выход перед входом и использует готовность codec.
KEY/decoder ожидания разделены; старт clock/audio после первого decoded frame, low latency при поддержке.
Защитный guard учитывает наблюдённый decoder lead; EOF освобождает inflight metadata и не блокирует audio tail.
Desktop использует тот же контракт: один рабочий AVPacket, два готовых изображения, EAGAIN сохраняет вход,
FIN дренирует decoder до EOF. Кодек/rotation меняют video revision, аудиошкала сохраняется.
151 тест ядра без ошибок/пропусков, proxy/integration/burst/load прошли; check.sh — 126s.
Четыре CTest stream/audio timing прошли; восемь Kotlin pump-сценариев прошли, APK обоих ABI и vibechat собраны.
Проверены 4/8 входных кадров при замороженном clock, сетевые паузы, поздний ответ чтения, пустой EOS и guard.
Настоящие FFmpeg кадры с задержкой выхода в четыре кадра: запуск после 80ms и полный drain короткого потока из трёх кадров.
Реальный AV1 на SM-A525F: первый decoded frame через 67–117ms после KEY в проверенных запусках,
длительный показ и поворот работают; завершённый запуск показал 21609/21609 decoded/shown, без discarded.
Исправленный APK установлен на SM. Сетевой протокол и формат RS остаются прежними; colo2/Q8 не обновлялись.
Архитектура: `doc/stream.txt`, `doc/service_lifecycle.md`.
Логи: `/tmp/av1-pump-{full-check,ring-test,bridge-test,service-test,gui-tests,android-final-build,sm-live}.log`.
[+] **Live: публикация с desktop ChatGUI** — 14.10.2026:
Камера, микрофон, экран и окно через Qt >=6.6 Multimedia/FFmpeg; Wayland/XWayland использует системный портал.
Отдельный controller сохраняет эфир при закрытии окна, сырое превью обновляется до 30Hz независимо от encode.
Выбор рабочего H.264/H.265/VP9/AV1/H.266, разрешения/FPS, bitrate и поворота 0/90/270 с отметкой верха.
Encoder проверяет два принудительных KEY, затем открывает чистую сессию; hardware имеет приоритет.
Смена видеонастроек сохраняет capture, звук и source_epoch; libx264 меняет bitrate без смены encoder.
Ошибка видеорежима откатывает последнюю подтверждённую конфигурацию; fatal Opus завершает эфир и освобождает микрофон.
Stereo Opus/128kbps с общим gain и текущими настройками компрессора рации; mono input обозначается явно.
Общий C stream_publisher для Android/desktop: ограниченные очереди/admission/KEY recovery, uAV2/RS, FIN/drain 2s,
согласованные 100ms TX snapshots (video queue, all-connection inflight, send buffer, ACK_1s). Протокол не меняется.
Microphone lease публикации эксклюзивен, прежняя совместная работа голосовых функций сохраняется.
151 тест ядра и proxy/integration/burst/load прошли; полный check.sh — 129s. Все 32 CTest ChatGUI прошли.
Publisher отдельно: 11 проверок прошли, экранный сценарий запущен отдельно под Xvfb/private D-Bus — без пропусков.
Проверены реальные H.264/H.265/VP9/AV1 encoder/decoder, сохранение decoder при изменении libx264 bitrate,
настоящий X11 screen/window capture, непрерывный неподвижный экран, сырое превью, два стереотона,
H.264 → VP9 и rollback без смены audio/source_epoch, Opus failure, FIN и concurrent detach/leak count.
vibechat и APK arm64-v8a/x86_64 собраны, Kotlin unit tests прошли; Windows runtime/installer fixtures прошли.
Физическая камера, захват через Wayland portal и Windows runtime вручную не проверялись; VVC encoder отсутствует.
Устройства/colo2 не обновлялись. Архитектура: `doc/stream.txt`, `doc/service_lifecycle.md`.
Логи: `/tmp/desktop-stream-{full-check,final-gui-tests,final-publish-test,screen-test,android-build,runtime-test,installer-test}.log`.
[+] **Live: выбор видеокодека/битрейта и статистика отправки 10Hz** — 14.10.2026:
Android показывает доступные Surface encoder в порядке H.264/H.265/VP9/AV1/H.266,
проверяя профиль, разрешение/FPS и диапазон bitrate; аппаратные encoder имеют приоритет.
Битрейт выбирается отдельно (Авто либо пресет), LIVE обновляется без перезапуска encoder,
при отказе используется смена видеоконфигурации с rollback. Звук и source_epoch сохраняются.
uAV2: прежний 32-byte header, пять video_codec IDs, KEY = config_length:4BE/config/access unit.
Конфигурация повторяется при каждом KEY и защищена общей подписью/RS; оба плеера выбирают decoder по codec ID.
NAL parameter sets объединяются без дублирования, partial codec config собирается с пределом 64KiB.
Publisher каждые 100ms показывает остаток video queue, общий inflight, полный send buffer и ACK_1s.
Буфер учитывает отдельные очереди video/audio, ожидающие RS-выдачи и ETCP/normalizer без двойного учёта input_send_q;
историю кольца не включает. ACK всех соединений учитывается один раз при подтверждении найденного пакета;
окно — десять завершённых buckets по 100ms, переживает REINIT/close пира и не требует BBR link.
Статистика имеет отдельный StateFlow; строки не меняют высоту превью при обычном изменении чисел.
Реальные H.264/H.265/VP9/AV1, отдельная decoder configuration, FIN и смена revision проверены.
H.266 проверен на уровне формата/KEY; реального VVC encoder в тестовом окружении нет.
Устранена блокировка нового desktop viewer старыми control records в лимите подготовки медиа.
151 тест ядра прошёл без ошибок/пропусков; check.sh с proxy/burst/load — 119s.
Qt stream/audio timing и Kotlin-проверки прошли; vibechat и APK arm64-v8a/x86_64 собраны.
Устройства/colo2 не обновлялись. Протокол и формулы: `doc/stream.txt`, lifecycle: `doc/service_lifecycle.md`.
Логи: `/tmp/stream-codecs-{full-check,final-gui-tests,android-build,media-test,tx-test,native-test}.log`.
[+] **Live: воспроизведение из RS-кольца и минутная адаптация задержки** — 14.10.2026:
Android и desktop используют общее кольцо кодированных данных, метаданные PTS/KEY и отдельные
cursor каждого reader. Пауза player не останавливает live ingest/selector; длинные кадры собираются
ограниченными проходами с отложенным продолжением без ожидания новых пакетов.
Убрана фиксированная стартовая буферизация 150ms; вне кольца остаются ближайшие кадры и рабочая порция PCM.
Общий контроллер хранит 60 секундных минимумов запаса. История корректируется по фактическому
простою/услышанному материалу, включая аппаратную очередь и расход ранее поставленного ускоренного PCM.
После полной здоровой минуты плавный темп до x1.1 уменьшает задержку до безопасного остатка;
SoundTouch сохраняет высоту звука и стереобаланс. Обычная нехватка сохраняет decoder references;
GAP, вытеснение и разрыв медиапоследовательности начинают новый KEY и новую историю.
FIN дочитывается каждым reader; close завершённого publisher сохраняет кольцо для локальных owners.
Проверены шестисекундная пауза сетевого reader без restart, точные remote/local FIN drain,
независимые cursors, поздняя повреждённая RS-часть, большой кадр, разрывы, конфигурация и границы PTS.
Реальные H.264/Opus: 30fps при audio latency 200ms, preroll, revision/rotation, stereo x1.1 без изменения тона,
физические аудиочасы, громкость и баланс памяти. Диагностика показывает запас/минимум, коррекцию, темп и wait/resume.
150 тестов ядра прошли без ошибок/пропусков; полный check.sh с proxy/burst/load — 129s.
Три Qt stream/audio timing теста прошли; vibechat и APK arm64-v8a/x86_64 собраны.
Устройства/colo2 в этой задаче не обновлялись. Архитектура: `doc/stream.txt`, lifecycle: `doc/service_lifecycle.md`.
Логи: `/tmp/stream-playback-{clock-test,full-check}.log`, `/tmp/stream-final-{gui-tests,desktop-build,android-build}.log`.
[+] **Live: переменный размер RS и одна подпись внутри пачки** — 13.10.2026:
RS принимает фактическую длину, уменьшает все части одной пачки до одинакового минимального размера
и дополняет максимум k-1 нулями; один контекст/матрица используются повторно, без аллокаций RS на encode/decode.
Источник подписывает метаданные и содержимое один раз, затем RS защищает сообщение вместе с 64-байтной подписью.
Проверка источника выполняется после восстановления; неверная подпись/подписанные поля дают один GAP без закрытия owner.
Следующая корректная пачка принимается. Ретранслятор пересылает части до decode, сохраняя малую задержку.
Дескриптор хранит максимальный размер, кольцо/очереди/метрики — фактические длины; слоты по-прежнему резервируются по максимуму.
При 6+2 содержимое 1000 байт даёт части 243 байта вместо 1300; полная вместимость содержимого — 7345 байт.
Wire version=5; бинарники участников трансляции нужно обновить вместе. Формат описан в `doc/stream.txt`.
Проверены все 28 пар потерь, малые/полные пачки, подмена содержимого/подписи/поля времени/индекса,
padding, неверный ключ, фактический учёт байтов, пересылка до проверки и продолжение после отвергнутой пачки.
149 тестов ядра прошли без ошибок/пропусков; полный `check.sh`, включая proxy/burst/load, завершился за 131 с.
Desktop stream/audio timing прошли; vibechat и APK arm64-v8a/x86_64 собраны, новый encoder проверен в обеих native-библиотеках APK.
Логи: `/tmp/stream-batch-{core,media,service,android-native,full-check}.log`,
`/tmp/stream-batch-{desktop-build,gui-tests,apk-build}.log`.
[+] **Live: callback обнаружения поставщиков** — 13.10.2026:
Принятые изменения Merkle и BGP помечают roster оптимизаторов соответствующего канала устаревшим.
Сверка/пробы выполняются после callback одним объединённым отложенным проходом в uasync.
Новый доступный поставщик опрашивается сразу; существующие пробы, цикл и backoff ошибок сохраняются.
Временно пустой буфер повторно опрашивается через 100 мс в пределах фиксированных 5 с,
чтобы ранняя подписанная опция не оставляла недостающие субпотоки до обхода через 30 с.
Регрессия с настоящим Merkle/BGP и задержанным READY: до исправления проверка задержки падала,
после принятого объявления INFO получен через 6 мс. Повторные callbacks не размножают пробы;
общий старт 8/2 и освобождение всех tracked allocations проверены.
149 тестов ядра, 43 Android-теста и desktop stream/audio timing прошли; GUI и APK собраны.
Логи: `/tmp/stream-provider-callback-{before,retry,full-check}.log`,
`/tmp/stream-provider-callback-{gui-tests,android-build}.log`.
[+] **Live: выбранная выдача отменяла помощь до READY/ACCEPT** — 13.10.2026:
Первое открытие desktop 17:07:41.035 → первый кадр 17:07:44.500 заняло 3.464 с;
запрос помощи отправлен через 99 мс, первый FEC через 2.490 с, KEY ещё через 0.796 с,
prefill/показ заняли 0.178 с. Повторное открытие заняло 0.983 с.
Merkle объявил буфер colo2 до READY: selector выбирал его INFO, но отменял STARTING-запрос,
потому что выдача ещё не ACTIVE. Четыре CANCEL закрыли собственную выбранную выдачу.
Запрос теперь удерживается также выбранными CONNECTING/INFO/REQUESTED upstream; INFO-пробы,
ENDED/CLOSED и чужие downstream не удерживают его. Аренда/idle и проверки поколений сохранены.
Регрессия с задержанным READY и настоящим Merkle/ETCP воспроизводит ошибку до исправления
и проходит после него без преждевременного CANCEL и tracked allocation leaks.
На colo2 всё ещё работает 14e2cdc2: опрос субпотоков раз в 100 мс и секундная задержка STATUS.
В сохранённом запуске helper генерировал STATUS в 18:07:42.465 и 18:07:43.476;
для полного ускорения нужен новый бинарник сервера. Логи: `/tmp/stream-first3s-{desktop,colo2,sm}.log`,
`/tmp/stream-first3s-regression-{before,after}.log`, `/tmp/stream-first3s-colo2-version.log`.
[+] **Live: ускорить начальное подключение** — 13.10.2026:
Дедлайн помощи 100 мс и первый пригодный INFO выбирают поставщика; DATA использует один общий старт.
Управление просыпается по событиям, подтверждение поколения и новые состояния помощи не ждут 1 с.
READY требует восстановленной FEC-пачки. Регрессии: прямой/relay победитель, поздние ответы,
отмена до дедлайна, пустой источник, дубликаты, общий индекс 8/2 и освобождение ресурсов.
Начальная очередь аудио прогревает Opus и обрезается по первому KEY; prefill 150 мс начинается после него.
149 тестов ядра, 43 Android-теста и desktop stream/audio timing прошли; GUI и APK собраны.
В сетевом тесте первый восстановленный блок через помощника получен за 192 мс, n=8/r=2.
Проверены поздний прямой INFO, отмена запроса до READY, отсутствие помощи для пустого доступного источника
и отсутствие дополнительных STATUS на повторы REQUEST. Все tracked allocations освобождены.
Desktop-регрессия прогревает 800 мс реального stereo Opus до KEY: звук молчит до начала видео,
затем сохраняются 30fps, stereo balance и смена revision при физической audio latency 200 мс.
Логи: `/tmp/stream-startup-full-check.log`, `/tmp/stream-startup-service-final.log`,
`/tmp/stream-startup-desktop-tests.log`, `/tmp/stream-startup-android-final.log`.
Контракт: `doc/stream.txt`, раздел 21. Замеры приведены для тестового стенда.
[+] **Live: сквозное стерео и три компактные кнопки поворота** — 13.10.2026:
CAMCORDER/Opus/компрессор/Android AudioTrack/desktop output используют два канала,
Opus AUDIO 128kbps. audio_codec=2 обозначает stereo; 960 кадров на канал дают 1920 PCM-отсчётов.
Capture/playback timestamps считают кадры на канал; mono-пакеты upmix выполняет libopus.
Три кнопки 0/90/270 подсвечивают выбранную и показывают верх цветной гранью на самой кнопке;
положение грани учитывает камеру и экран, наложение на превью удалено.
148 тестов ядра, 43 Android-теста и desktop stream/audio timing прошли.
Проверены stereo balance после AGC/Opus и независимые каналы на audio device (отношение энергий 15.99 при исходном 16).
APK/чатгуи обновлены; SM подтвердил channels=2 и ненулевой lrDifference.
Повороты 90/270 и возврат 0 проверены во время публикации без смены audio codec handle/epoch;
desktop показал ~30fps. Логи: `/tmp/stream-stereo-sm-final.log`, `/tmp/stream-stereo-final-desktop-tests.log`.
[+] **Android live: CAMCORDER и компрессор с настройками рации** — 13.10.2026:
AudioRecord.CAMCORDER, 48kHz mono; общий audio_compressor перед Opus AUDIO/64kbps,
lookahead=0, без изменения PTS. DataStore передаёт включение, max_gain и общий rise_rate
из настроек рации в audio worker; обновления применяются без перезапуска захвата/эпохи.
43 Android-теста и 631 проверка компрессора прошли, включая реальный Opus roundtrip,
live bypass, смену gain, отсутствие накопления output и утечек. APK установлен на SM.
На SM подтверждены CAMCORDER(5), переключение on/off/on в одном codec handle и восстановление
исходных enabled=true/max_gain=45/rise=6. Desktop продолжил показ ~30fps.
Лог: `/tmp/stream-camcorder-sm-final.log`.
[ ] **Общий AGC: клиппинг после резкого роста сигнала при max_gain=45dB** —
на SM при проверке CAMCORDER с настройками рации rise=6/release=30 наблюдались
4581 обрезанный отсчёт за интервал около 1s. Это общий алгоритм radio/stream,
плавное снижение gain может не успеть за скачком входа. Проверить ограничение пиков
без щелчков и ухудшения адаптации. Лог: `/tmp/stream-camcorder-sm-live.log`.
[ ] **Desktop: выход после SIGTERM оставляет GUI после остановки ядра** — 13.10.2026:
при обновлении live-плеера старый vibechat завершил ядро/worker, но процесс остался
в QCoreApplication::exec с работающим audio output. В логах также DM SELECT после
уничтожения SQLite (`bad parameter or other API misuse`). Проверить отмену Quit
в MainWindow::closeEvent (всегда ignore), порядок уведомлений и отключение GUI от БД.
Снимок: `/tmp/stream-desktop-shutdown-backtrace.log`, лог vibechat 13:04:09.
[+] **Desktop live: очередь видео вытесняла будущие кадры до показа** — 13.10.2026:
SM → colo2 доставлял ~30fps, desktop декодировал ~30fps, но videoQ=6 постоянно
выбрасывал ближайший кадр до достижения его PTS аудиочасами. Регрессия H.264/Opus
30fps с физической audio latency 200ms на старом коде не показала ни одного кадра.
Видео ограничено 128 кадрами/256MiB; при пределе сохраняются ближайшие к показу.
Добавлены decode/show FPS, late/pressure и PTS очереди относительно часов;
поштучное нормальное вытеснение транспортного буфера перенесено в trace.
Исправленный test_stream_view: 8/8; test_audio_timing прошёл. Живой просмотр
SM → colo2 после обновления: средний showFps=29.81 за 40s, videoQ=8–9, pressure=0.
Метрики: `/tmp/stream-desktop-fixed-live-metrics.log`; краткие просадки showFps до 22.4 остаются наблюдаемыми.
[+] **Android: категории диагностики и обрезанный текст уровня** — 13.10.2026:
список берётся из native-ядра, включая stream/video/reality/dm/call/aec.
Поля уровня используют минимальную высоту вместо фиксированных 48 dp;
изменения диагностики применяются сразу без перезапуска при выходе из настроек.
Общий уровень сохраняется в [global] debug_level; оба парсера принимают все категории,
а превышение лимита логируется. Регрессия: test_config_debug, файл и INI-текст.
[ ] **Стриминг SM → colo2 → desktop: восстановление частично остановившихся субпотоков** —
13.10.2026: помощь подключилась за ~8 с, все 8 субпотоков через colo2 приняты с общего индекса 5844.
Позже шесть субпотоков перестали продвигаться около seq=111625; четыре restart не восстановили
просмотр, desktop завершился по таймауту в 11:10:48 (colo2: 12:10:48). На SM публикация продолжилась,
colo2 снял свою подписку через 3 с. В том же интервале сервер видит RX timeout/переподключения TCP к SM;
первопричина остановки выдач пока не установлена: stream на colo2 наследовал error, детальных логов не было.
Повторить с заранее включённой диагностикой stream/etcp на SM и colo2, проверить upstream handles,
аренды, позиции и повторное согласование. Логи: `/tmp/stream-live-gui-final.log`,
`/tmp/stream-live-colo2-final.log`, `/tmp/stream-live-sm-final.log`.
Повтор 13.10 со stereo: до 15:20 desktop показывал ~30fps, затем video/audio очереди опустели;
decodeFps упал до 7–18 и viewer завершился по таймауту в 15:21:13 после пяти restart.
SM при этом сохранял ~30fps без dropped. Новые логи: `/tmp/stream-stereo-desktop-delivery-stall.log`,
`/tmp/stream-stereo-colo2-delivery-stall.log`, `/tmp/stream-stereo-sm-delivery-stall.log`.
[+] **Стриминг: помощь суперузлов по запросу** — 13.10.2026: автоматический запрос
при невозможности получения k субпотоков, внутреннее владение точной эпохой,
общий буфер со зрителями, аренды и остановка без downstream. Relay-only
экземпляры не удерживают друг друга. Подписанное управление с подтверждением
поколения, таймаут READY, защита от поздних запросов и пустой END.
Routed ID перенесён на 0x3C из-за конфликта с MEMBER_FILES; wire v4.
Исправлено владение CM handles репликации медиа при смене роли суперузла.
Контракт: `doc/stream.txt`, раздел 21. 148 тестов прошли, 1 пропущен;
`test_stream_service` и отдельный `--relay-only` завершились без утечек.
[ ] **Android: отмена асинхронного запуска до готовности ядра** — при проверке
мониторинга 12.10.2026 обнаружено: `NativeLib.stop()` пропускает native stop,
если `isReady()==false`, а `instance_lite_stop()` возвращается при `g_inst==NULL`.
Поэтому запрос stop до завершения конструктора может не отменить запускаемый
поток. Согласовать отмену с состоянием потока и единым сигналом завершения;
проверить stop до SERVICE_STARTED, отказ конструктора и последующий start.
[ ] **Android: повторные отказы persisted node snapshots** — замечено 12.10.2026
на устройстве D2AR10P3600820 при проверке live media. В сохранённой БД ряд узлов
регулярно получает `invalid persisted snapshot` в `topo_node_sqlite_snapshot_load`,
затем NCD сообщает `node not found`; повторные попытки засоряют лог.
Лог: `/tmp/stream-av-device-initial.log`. Причина пока не установлена: текущий
текст объединяет отказ deserialize, несовпадение ID и отказ verify. Разделить
диагностику этих ветвей и проверить повторные попытки загрузки отвергнутого снимка.
Существующую БД устройства при проверке стриминга не очищали.
[+] **STUN IPv4: резервное определение типа NAT по IP/netif** — уточнённый план
12.10.2026 в `doc/nat_detection.txt`. Одна группа и отдельный тестовый UDP-порт
на уникальный IP/netif, общий кэш с generation/TTL. При NO_OBSERVER/NO_HELPER
применять пригодную оценку STUN с явным source; измеренный ETCP приоритетен.
mapped адрес тестового сокета не переносить на рабочий. Проверить дубликаты,
переход STUN -> ETCP, различия по портам и жизненный цикл; namespace-стенд.
Реализовано 12.10.2026: группировка, кэш/TTL, история портов через reload,
source в подписанном NODEINFO/SQLite/мониторе и приоритет частичного ETCP.
145/145 тестов; три ASAN-теста без утечек; реальные cone/restricted/dependent
NAT и переключение STUN -> ETCP прошли в изолированном стенде.
[ ] **STUN IPv6** — отдельный этап: IPv6 endpoint/config/DNS и XOR-MAPPED,
scope/netif, тесты IPv6/NAT66. IPv4-оценки не переносить на IPv6 или TCP.
[ ] **check.sh: изоляция сетевых integration-тестов** — после основного набора
автоматически запускает check-proxy/burst/load при доступном sudo. Скрипты
меняют rp_filter, таблицу 100 и iptables главного namespace; cleanup выставляет
rp_filter=1 вместо сохранённого значения. Перенести тесты в отдельный namespace
и проверять неизменность родительской сети, как в tests/stream_netns.
[ ] **Стриминг: реализация по stream.txt** — 10.10.2026:
ТЗ и этапы: doc/stream.txt, разделы 15–16. Основа данных реализована в src/stream:
подписанные FEC-блоки 1300 байт, общий буфер 32 МБ, относительные timestamp-метрики,
источник с таймером 100 мс и категория stream. test_stream_core прошёл;
./check.sh: 143 passed, 0 failed, 0 skipped; proxy/full, burst и load — PASS.
11.10.2026 подключены жизненный цикл CHAT, локальные handles/API и события потребителя,
атомарные опции мембера через Merkle, подписанные профили и BGP-фильтр кандидатов.
test_stream_service прошёл на четырёх ядрах: две группы, смена эпохи/членства, restart,
rollback профиля, закрытие из callback и нулевой остаток аллокаций после destroy.
Лог: /tmp/stream-service-test-final.log. Исправлена выявленная утечка BGP node_paths при удалении узла.
Общий ./check.sh: 144 passed, 0 failed, 0 skipped; proxy/full, burst и load — PASS.
Лог: /tmp/stream-service-check.log (107 с).
11.10.2026 подключена сетевая выдача raw ETCP через собственный NCD owner: INFO/поколения,
handles/watermark, общий старт от первого DATA, курсоры/пути/GAP/LOOP, подпись END и аренда/RENEW.
Routed REVERSE_REQ/CLOSED подписываются end-to-end. Явные receive API позволяют открыть/закрыть выдачу и начать новую попытку.
Сетевая цепочка источник -> relay -> получатель, две группы/FEC на одном ETCP, GAP/restart,
повторы/reverse, END, lease timeout и REINIT прошли под ASAN/LeakSanitizer без ошибок/утечек.
Логи: /tmp/stream-network-asan-core.log и /tmp/stream-network-asan-service.log.
После восстановления обычной сборки ./check.sh: 144 passed, 0 failed, 0 skipped;
proxy/full (9/9), burst и load — PASS. Лог: /tmp/stream-network-check.log (88 с).
Wire и численные пределы зафиксированы в doc/stream.txt, раздел 17.
11.10.2026 подключены timestamp-метрики/rates/stops, nonce/метрики INFO и RTT uint32 мс,
A/D/X/Q выдач/пиров/модуля, интервалы 200мс и история 10с, подтверждение перегрузки 500мс.
OVERLOAD с frozen rates, FEC/альтернативами при выборе, SELECT/CLOSED, неизменным сроком 2с и fallback;
глобальный бюджет нескольких пиров требует backlog и локальной загрузки, CPU отдельно не режет.
4-секундный предел возраста control/ожидающих заданий, GAP/X при истечении и наблюдаемый статус accepting.
Частичная/сильная/глобальная разгрузка, короткий глитч и поздний SELECT прошли под ASAN/LeakSanitizer без ошибок/утечек.
Логи: /tmp/stream-pressure-asan-core.log и /tmp/stream-pressure-asan-service.log.
Обычная сборка восстановлена; ./check.sh: 144 passed, 0 failed, 0 skipped;
proxy/full (9/9), burst и load — PASS. Лог: /tmp/stream-pressure-check.log (90 с).
Wire версии 2 и пределы обновлены в doc/stream.txt, раздел 17.
11.10.2026 подключён автоматический планировщик: stream_subscribe_open начинает получение,
ручной режим выделен в stream_subscribe_manual_open. INFO-only пробы, цикл 32/1с/30с,
проверенные пути/RTT/метрики/слоты, backoff, распределение FEC, перенос с перекрытием и failover.
Первый DATA задаёт общий N; пустой источник и курсор last+1 ждут данные, END без DATA также подписан и доставляется.
Сохранён желаемый режим frontend; stream owners удерживают ACTIVE до close, затем восстанавливается STANDBY.
Статус/настройки — stream_selection_get_info/configure; wire версии 3 и пределы — doc/stream.txt, раздел 17.
На четырёх ядрах проверены 400 непрерывных пачек, общий планировщик владельцев, цикл 30с,
FEC spread, overlap до проверенного DATA, остановка relay/CLOSED и восстановление без разрыва ETCP.
Core/service, NCD и standby transport прошли ASAN/LeakSanitizer; остаток аллокаций — ноль.
Логи: /tmp/stream-optimizer-asan-{core,service,ncd,standby}.log.
Односторонняя смена stream generation восстанавливается по nonce INFO без разрыва ETCP и вращения эпох другой стороны;
регрессия и весь service-тест прошли ASAN/LeakSanitizer: /tmp/stream-generation-asan-service.log.
./check.sh: 144 passed, 0 failed, 0 skipped; proxy/full (9/9), burst и load — PASS.
Лог: /tmp/stream-optimizer-check.log (97с).
Следующий этап — длительные/масштабные сетевые проверки: NAT/reverse, мультилинк,
одновременная перестройка, несколько автоматических источников/групп и mobile standby на устройстве.
[+] **test_reality_tls: коллизии порта слушателя** — 10.10.2026:
В общем прогоне 140 passed, 1 failed: stcp_server_create не смог занять порт 34922, bind errno=98 (EADDRINUSE).
Причина: выбор порта как 34000 + time(NULL) % 1000 без гарантии его доступности.
Слушатель создаётся с port=0; stcp_server_get_port возвращает назначенный ОС порт для подключения клиента.
Логи показывают фактические порты REALITY и HTTPS. Сборка прошла; 8 параллельных TLS/HTTP-тестов прошли
при занятом всём прежнем диапазоне 34000–34999. ./check.sh: 141 passed, 0 failed, 0 skipped;
proxy/full, burst и load — PASS.
[ ] **Member files: исчерпание кеша результатов массовых изменений** — 10.10.2026:
Воспроизведено test_member_files_browser::recursiveDeletion(server-busy): из 76 объектов удаляются 64,
следующая команда получает MF_BUSY, остаются 12. storage.c: STORE_RECEIPTS=64, RECEIPT_TB=1200000 (120 секунд).
Лимит общий для успешных COMMIT/MKDIR/RENAME/REMOVE; затрагивает также рекурсивное копирование.
Нужен отдельный этап управления временем жизни receipts с сохранением идемпотентности повторных запросов;
простое вытеснение старых записей нарушает гарантию повторного ответа. После исправления изменить регрессию
на успешное удаление более 64 объектов и проверить повторные запросы/потерю ответа.
[ ] **Desktop: провалы вероятности VAD при компрессии** — диагностика 09.10.2026:
Воспроизведение пользователя: длительная гласная, затем временная нечувствительность; ближе к концу лога несколько повторов.
Новый лог 14:32:14–14:34:38: на 14:34:07–18 и 14:34:20–38 все 556 окон с RMS > -30dBFS имеют p < 0.5;
PCM непрерывен, errors=0, RX-gate не мешает, clips=5 не растёт. Сигнал не исчезает, порог остаётся 0.5.
--probe-stream: C-обёртка + реальный AGC + две Silero на одинаковом PCM, отличие — один reset состояния второй модели.
Синтезированный flite ah растянут до 24.576с: без AGC/клиппинга p первой секунды следующей речи 0.027 против 0.674 после reset.
При AGC 1dB (clips=0) — 0.012 против 0.662. Влияние истории модели подтверждено, провал на десятки секунд целиком не воспроизведён.
Лог стенда: /tmp/vad-vowel-context.log; запуск: test_silero_vad lib/silero_vad.onnx --probe-stream /tmp/vad-vowel-context48.pcm 993.
Вход стенда: mono s16le/48кГц, reset_window — индекс окна 512/16кГц; режим проверяет без AGC, 1dB и 15dB.
Автоматические сбросы в рабочем VAD не добавлялись; test_silero_vad PASS.
Уточнение: низкая вероятность держится десятки секунд, затем распознавание восстанавливается; краткий клиппинг это не объясняет.
Локальный лог 14:00:10–14:00:58: 15 сбросов AEC при пропусках референса, 16 clipped samples в VAD; vad DEBUG не был включён.
Добавлены секундные уровни raw/clean/reference и причина bypass/reset AEC; отдельное предупреждение о клиппинге VAD.
Секундный отчёт захвата работает также без PCM: возраст кадра, очередь, максимальная задержка и предупреждение о простое.
TRACE каждого окна содержит DC, число нулей и gain вместе с вероятностью/уровнем/паузой между окнами; видны сбросы и RX-gate.
В локальном build/vibechat.cfg включены vad=trace, aec/radio/call=debug, general=info; после диагностики вернуть обычные уровни.
Стенд --probe повторяет одинаковый PCM без reset: AGC 15/30dB после паузы обрезает пики (468/5625 samples на чистой записи).
Полного провала всей фразы почти до нуля стенд не воспроизвёл; нужен повтор живой речи с новыми логами для связи с AEC/AGC.
Алгоритмы обработки сигнала пока не менялись; исправление причины выбирается по результатам повтора.
[ ] **Linux headless CMake: актуализировать зависимости общего ядра** — 07.10.2026:
чистая сборка tools/chatgui-android/build_headless останавливается на lib/speex_aec.c:
speex/speex_echo.h не найден. utun_setup_sources включает этот исходник, но UTUN_INCLUDE_DIRS не содержит
lib/speexdsp/include. Проверить полный состав Speex/voice зависимостей отдельного каркаса;
основная Autotools C-сборка и Android NDK-сборка проходят.
[ ] **REALITY: исправить формат ClientHello и продолжение TLS-потока** — 07.10.2026:
supported_groups и signature_algorithms не содержат внутренние двухбайтовые длины списков.
После ClientHello/ServerHello идёт STCP без TLS record framing; это распознаётся пассивным наблюдателем.
[ ] **Завершить проверку реакций и голосов на SM** — 07.10.2026:
причина отказа установлена: у записей корней `signed_by=0/NULL` были нулевые/отсутствующие подписи дерева.
Проверка корня ключом канала остаётся обязательной; добавлены причины отказа и проверка доверенного Ed25519.
Исправлено применение подтверждённой подписи при равной/старой версии профиля и её сохранение при нулевом блоке
в более свежем профиле. Создание канала проверяет обе подписи корня до записи собственного мембера.
На local восстановлены шесть подписей канальными приватными ключами без изменения остальных полей записей.
Разовый скрипт и резервная копия: `/home/user/utun2-backups/root-certificates-20261007`.
Проверены все 18 участников local и 17 участников копии SM; тесты требуют отказа при нулевой/поддельной подписи.
Linux/Android/desktop собраны; `./check.sh`: 133 passed, 0 failed, 1 skipped; proxy/full, burst, load и 7 Qt-тестов — PASS.
Осталось установить APK на SM (`RZ8R72MG64B`) и проверить реакцию/голос на устройстве: SM отключился от ADB во время работы.
[ ] **rlottie: разбор предупреждений GCC 15 в импортированных исходниках** — 05.10.2026:
при сборке `tools/chatgui/rlottie_src` GCC сообщает `-Wuninitialized` в
`VArenaAlloc::reset()`: fFirstBlock/fFirstSize/fFirstHeapAllocationSize читаются
после явного вызова деструктора. Проверить сохранение значений до разрушения
и повторный reset с реальными выделениями. Также проверить `-Wfree-nonheap-object`
в `vcow_ptr`: статическая модель default_s сохраняет базовую ссылку.
Лог: `/tmp/utun-rlottie-vendor-build.log`. Сборка библиотеки и подключение её
экспортированного CMake-пакета к ChatGUI на Linux прошли; Windows ещё не проверен.
[ ] **ChatGUI Windows: подтверждение обновления runtime компилятора** — 05.10.2026:
`cc-probe.log`: cc1 запускается из корня, но в `tools/chatgui/build` завершается
с `0xC0000139`; локальные DLL GCC/math/runtime отличаются от UCRT64.
Перед обеими точками сборки добавлено обновление существующих копий этих DLL;
раскладка FFmpeg использует для них UCRT64. Проверены обновление, повторный вызов,
сохранение кодеков и ошибки. Требуется Windows-прогон `check_cc.bat`: cc1 после
обновления, повтор команды Ninja и сборка Opus должны завершиться с кодом 0.
[ ] **ChatGUI Windows: обновление остальных DLL приложения после обновления MSYS2** — 05.10.2026:
в `cc-probe.log` также отличаются Qt и другие DLL. `tools/chatgui/build.sh`
пропускает раскладку при наличии основного комплекта, без сравнения версий;
рекурсивная раскладка копирует только отсутствующие зависимости.
Runtime GCC обновляется отдельно; для остальных DLL нужно продумать обновление
с сохранением приоритета собственной сборки FFmpeg и проверить запуск приложения.
[ ] **Android: время жизни config provider** — 03.10.2026: `utun_bridge_init()`
передаёт адрес локального `utun_config_provider_t` в `utun_config_set_provider()`,
который хранит указатель. После возврата из init он недействителен; перед
использованием `cfg_get_*` обеспечить постоянное хранение provider.
[ ] **uasync: ложные ERROR об утечке перед cleanup** — 01.10.2026:
test_dm_e2e и существующий test_conn_mgr проходят, но uasync_destroy()
пишет Timer leak до освобождения отменённых heap/immediate таймеров.
После cleanup счётчики сходятся, финальная проверка проходит. Перенести
предварительные счётчики в DEBUG; ERROR оставить для реального расхождения
после cleanup, отдельно показывать активные и отменённые таймеры.
[ ] **Общий лимит check.sh после clean** — 01.10.2026: расчёт по времени предыдущего
прогона дал 205с вместе с полной сборкой и оборвал проверку после успешных тестов
до `test_uasync_socket_race`. Повторный запуск на готовой сборке с большим лимитом
прошёл за 206с, включая proxy/burst/load. Разделить бюджет сборки и запуска тестов
либо учитывать clean при расчёте.
[ ] **Паузы аудио Qt при звонке с SM** — 01.10.2026, звонок
`939a3ea36f3eeac3`, прямой LAN TCP `192.168.40.250:44604 ↔ 192.168.40.247:44173`.
Tcpdump: общие паузы входящего трафика до 645мс; ping до роутера в среднем
100мс, до SM 128мс. ПК на 2.4ГГц, SM на 5ГГц. Одновременно `CallAudio: cb`
падает до 20–40/с вместо 100/с, буфер Qt растёт до 2с, на SM повторяется refill.
В 8с strace аудиопоток продолжает обслуживать PulseAudio без долгих syscall;
причина редких аудиоколлбэков пока не установлена. Добавить измерения интервалов
и длительности callback, кадров capture/playback и сводку CALL_STATS; сравнить
сетевые задержки после подключения ПК к 5ГГц или кабелю.
Повторный звонок `b2a423ed2d36ed83` (01:25:31–01:27:39): DIRECT сохраняется,
RTT 2–3мс; в начале на SM gap tone длится 1.8–2.0с примерно каждые 6с.
На Qt частота callback падает вплоть до 4/с, входящий PCM продолжает приходить:
буфер 65→465→1465→2079мс за 3с, затем растёт до нескольких секунд, dropped=0.
Локализовано прекращение обслуживания duplex-аудио Qt; ожидание backend и
задержка обработки PCM пока не разделены. Часы SM отстают от Qt примерно на 1.24с.
Добавлена диагностика backend/устройств, времени между callback и длительности
capture/playback, фактической частоты callback и PCM кадров за интервал.
Счётчики сбрасываются до старта устройства; нужна повторная запись с новой сборкой.
[ ] **Оставшаяся задержка запуска аудио SM** — после обновления 01.10.2026,
звонок `2b9ad4e21a54d8ba`: стартового gap tone нет на обеих сторонах; Qt
стабильно работает около 100 callback/с, max_idle=15.3мс, буфер 55мс, RTT=2мс.
На SM первый capture появляется через 1140мс от startAudio (1164мс от ACCEPT).
router.start=781мс, получение снимка устройств=0мс, AudioRecord create=24мс,
startRecording=7мс; от начала startRecording до первого кадра=328мс.
Capture worker стартует раньше playback, но общий выигрыш пока не виден.
AudioTrack create=21мс, play=327мс; система сообщает AudioPortCache contention=497мс
и восстановление AudioTrack при setPreferredDevice. Разделить замеры регистрации
callback, BT-профиля и применения маршрута; отдельно проверить первый AudioRecord.read.
На SM входящий буфер за время подготовки вырос до 1149мс и к концу звонка снизился
до 570мс; ускорение старта playback также сократит начальное накопление.
Реализован независимый запуск AudioTrack и AudioRecord в play/capture потоках;
частичный старт отменяется до освобождения устройств/native. AEC измеряется после готовности обоих.
Добавлены callbackMs/btProfileMs/applyMs/refreshMs, длительность первого read и время первой записи playback.
Резерв теперь зависит от изменения задержки доставки по timestamps CALL_MEDIA, отдельно от глубины очереди;
стартовое накопление и догон не повышают запас. Сохранность FIFO и uint32 wrap проверяются регрессиями.
Сборки ядра, Qt и Android после clean успешны; три аудиотеста прошли. Полный check:
113 PASS, 0 FAIL, 1 SKIP (auto_socket_dynamic требует root); proxy/burst/load прошли.
Регрессия refill учитывает остаток SoundTouch: два новых кадра могут уже заполнить запас 60мс.
SM и Q8 обновлены 01.10.2026 в 03:03. Выигрыш по времени требует пробного звонка с новой версией.
[ ] **Гонка invite-файла в test_chat_join_e2e** — `wait_file()` проверяет только
существование файла, а `wf()` создаёт его до записи. Читатель иногда получает
пустую ссылку и ошибку префикса `utun://`. Публиковать готовый файл атомарно.
[ ] **Некорректное время в выводе check-proxy** — успешные `basic_1mb` и
`stress_sessions` иногда печатают огромные положительные/отрицательные миллисекунды,
хотя итоговая скорость и длительность передачи выглядят нормально. Проверить расчёт вывода.
[ ] **Linux ONNX Runtime: повторная регистрация схем при запуске теста** — 03.10.2026:
test_silero_vad при загрузке модели печатает серии `Schema error: ... already registered`.
Инференс, речь, тишина и reset проходят; отдельно проверить состав и загрузку Linux-библиотек ONNX.
Диагностика: `/tmp/vad-compressor-silero-tests.log`; Android-сборка таких сообщений не содержит.
[ ] **test_tcp_proxy_full: некорректное время в диагностике** — 03.10.2026:
полный check.sh проходит, но строки basic/stress/concurrent/half_close выводят
огромные или отрицательные миллисекунды (например, -1611883348002232012ms).
Отдельно проверить формат/аргументы вывода длительности; `/tmp/vad-compressor-check.log`.
[ ] **debug_parse_config: формат и индексы категорий** — parser ожидает `:`, документация
описывает `=`; преобразование битовой маски осталось после перехода enum на индексы.
[Разбор](etcp_full_suite_findings.md).
[ ] **DM: прямой p2p чат с любым пользователем группы** — отдельная DM-подсистема.
ТЗ и архитектура: `/doc/dm_arch.md`. Статус: реализовано (dm_core/dm_mailbox/dm_crypto),
тесты `test_dm` (крипто) и `test_dm_e2e` (интеграция). Остались задачи ниже.
[ ] **Звонок (P2P аудио)** — голос 1:1, из CHAT-группы или p2p-чата (DM). Отдельный
каталог `src/call/`. Сигналинг + conn_mgr + аудиопоток (Opus) с backpressure
(локальный буфер отправки при переполнении роутера) + адаптивный джиттер-буфер
(SoundTouch, ускорение до 1.6x) + тоны (глитч/завершение) + watchdog 20с.
Android: переключение источника (трубка/громкая/гарнитура).
Архитектура: `/doc/chat_call_arch.md`.
[ ] **DM-звонки** (fallback `group_id==0`) — не сделано: `call_is_online`/`call_start`
требуют реальную группу (`topo_groups_find`), для DM (group_id==0) маршрута нет.
## Рефакторинг: per-instance chat/dm (однопоточный test_dm_e2e)
[ ] **BGP-гонка: дропнутый REQUEST_TABLE** (отдельная задача, как договорились).
**Место:** `topo_group_new_conn()` в `src/routing_layer/topo_group.c:571`. Дедуп-ветка
«conn already in senders_list» (строки 579–587) делает `return` БЕЗ повторной отправки
`topo_group_send_table_request()`.
**Зачем дедуп:** тот же `ETCP_CONN` стреляет `ETCP_CONN_STATUS_UP` дважды (UDP-линк,
затем TCP-линк). Повторная обработка задваивает `active_conn_count`, из-за чего
переподключение не стартует. Поэтому `return` в дедупе — правильный.
**В чём гонка:** REQUEST_TABLE — это то, что заставляет пира отдать свою таблицу
(NODEINFO/узлы группы). Сценарий «разнесённого старта»:
1. A↔C соединение поднято. A шлёт C `REQUEST_TABLE` для группы G (`topo_group_new_conn`
→ `topo_group_send_table_request`, `topo_group.c:602`).
2. У C группа G ещё **не создана** (создаётся позже, по мере загрузки каналов/синка).
C получает `REQUEST_TABLE`, но `topo_group_handle_request_table` не находит G →
дропает запрос.
3. C наконец создаёт G (`topo_groups_create_group` → обход connections →
`topo_group_new_conn(G, conn_A)` на стороне C) → C шлёт A свой `REQUEST_TABLE`.
A отвечает своей таблицей → **C узнаёт A**.
4. Но A больше не шлёт C `REQUEST_TABLE` → **A так и не узнаёт C** в группе G.
Итог: при разнесённом старте A не видит C в CHAT-группе (односторонняя видимость).
**Фикс (предполагаемый):** в дедуп-ветке не просто `return`, а предварительно
идемпотентно `topo_group_send_table_request(group, conn)`:
```c
if (((struct TOPO_GROUP_CONN_ITEM*)se->data)->conn == conn) {
topo_group_send_table_request(group, conn); /* повторно, идемпотентно */
DEBUG_INFO(..., "conn already in senders_list, re-request table (%s)", conn->log_name);
return;
}
```
`topo_group_send_table_request` не ведёт состояния — просто шлёт REQUEST_TABLE,
пир отвечает снимком таблицы (идемпотентно). Лишний дубль таблицы безвреден.
**Замечание:** в однопоточном `test_dm_e2e` (и `test_chat_join_e2e`) из-за
детерминированного порядка группа успевает создаться до прихода REQUEST_TABLE —
поэтому тест проходит. Реальная гонка остаётся для асинхронных сетей. Отдельный
воспроизводимый тест для этой гонки пока не написан.
[ ] **«packet undecryptable» на линках C** (вторичное). A, узнав B через BGP, через
`topo_group_connect` пытается поднять прямой A↔B линк (`seskey 4777737b`), но B не
настроен принимать A → crypto-шум каждые ~1с. Разобраться с авто-подключением
(не пытаться соединяться с узлами, для которых нет `[client]`/ncd-конфигурации,
либо обрабатывать неудачу без спама).
[ ] **lwip/proxy: утечка pcb в FIN_WAIT_2** — `tcp_shutdown(pcb,0,1)` (half-close по FIN от
exit) не ставит `TF_RXCLOSED`, поэтому таймаут FIN_WAIT_2 в `tcp_slowtmr` не срабатывает:
если браузер никогда не пришлёт FIN, pcb навсегда остаётся в FIN_WAIT_2 (утечка, не crash).
Двойной free по TIME_WAIT уже закрыт (detach по локальному закрытию + guard abort).
## Открытые флаки
[ ] **test_etcp_reconnect** — флаки под параллельной нагрузкой `make check -j4`:
phase 4 (reconnect после server restart) таймаутит (`sent=480 recv=0`), при одиночном
запуске стабильно `TEST PASSED`. Наш код (chat_join/chat_sync) его не трогает.