Files
utun2/doc/stream_audit_findings.txt
T

524 lines
48 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Аудит документации src/stream — 26.10.2026, заголовки завершены
Текущий статус: deleted исправлен и покрыт штатным test_stream_member; сборка,
профиль7/7 и check.sh195/195 PASS относятся к внедрению этого исправления (раздел 5).
При продолжении аудита подтверждена потеря помещающегося Opus в publisher_block (раздел 6).
Исправление внедрено: сборка и штатный test_stream_publisher23/23 PASS; после
объединения origin/master профиль8/8 и check.sh200/200 PASS (раздел 7).
Остальные частичные/непроверенные контракты каталога не закрыты. Разделы 1–4
описывают первоначальную диагностику и проверку архитектуры до внедрения deleted.
В продолжении полностью прочитаны stream.c и stream_optimizer.c, уточнены их
заголовки; теперь6[V]/12[~]/0[ ]. В частично прочитанном network подтверждён
дефект истечения INFO_REQ (раздел8). Исправление и штатные проверки выполнены:
TTL4/4, обе реальные backpressure-сцены и check201/201 в отдельном IPv6 namespace PASS (раздел9).
Текущий итог продолжения:18[V]/0[~]/0[ ] для заголовков src/stream; полностью
прочитаны все16 реализаций. Раздел10 описывает финальную сверку контрактов.
doc/stream.txt остаётся[~] из-за неполной проверки платформ desktop/Android
и внешнего многопроцессного стенда. Старые показатели выше и в разделах1–9 — история отдельных этапов.
1. Подтверждённый дефект: deleted не учитывается stream_member_load
src/stream/stream_member.c:166–167 выбирает строку только по node_id. Условие
source=0 проверяется ниже, но столбец deleted не выбирается и не проверяется.
Проверка дерева приглашений и подписи профиля не заменяет проверку блока владельца:
после принятого deleted=yes эти подписи участника остаются корректными.
Поэтому loader возвращает STREAM_OK и публикует прежние stream-опции/ключ.
Это расходится с допуском CHAT (source=0, deleted=0), закреплённым в topo_group.h,
и с результатом topo_node_sqlite_member_allowed. stream_member_supernode уже
отбирает deleted=0. Существующий тест deleted проверяет только supernode lookup.
Обработчик member_changed в stream.c:539–546 использует stream_member_load:
данный результат не запускает ветку инвалидирования STREAM_CLOSE_MEMBER.
Для исходной реализации полный сетевой сценарий продолжения выдачи после удаления не проверялся;
обход сетевой авторизации этим воспроизведением не заявляется.
Изолированная проверка
Настоящее ядро с SQLite :memory:, отдельные ключи канала и тестового участника.
Join/update/tree подписи создаются штатными функциями и принимаются member_sync_put.
В профиле корректные stream_source/subscriptions. Участник отличается от локального
узла; никаких удалённых узлов или пользовательской БД проверка не затрагивает.
Затем через member_sync_put применяется подписанный ключом владельца блок
{"ver":"1","deleted":"yes"}; прямой UPDATE deleted для воспроизведения не используется.
Фактический лог:
before load=0 allowed=1; after load=0 allowed=0 published=1; expected load=-9
Тест завершился exit=1 на несовпадении с ожидаемым STREAM_ERR_NOT_MEMBER.
Стенд: /tmp/stream-audit-member-deleted.c.
Сборка через существующие Makefile/LIBUTUN/COMMON_LIBS и дополнительную цель:
make -C tests -f Makefile -f /tmp/stream-audit-member.mk /tmp/stream-audit-member-deleted
Логи: /tmp/stream-audit-member-build.log, /tmp/stream-audit-member-deleted.log.
Runtime выполнен вне sandbox из-за ограничений сокетов uasync.
Предлагаемый вариант исправления
Добавить deleted в список полей этого же SELECT и до проверки профиля вернуть
STREAM_ERR_NOT_MEMBER для удалённого участника, с отдельным логом причины и ID.
Вторая SQL-проверка и новый API не требуются. Вариант WHERE deleted=0 тоже
отсечёт запись, но существующий лог "member absent" не отличит удаление от отсутствия.
Сохранить различие отсутствующего/удалённого участника и неполного профиля (WAIT),
атомарность state/public_key при отказе.
Регрессия должна проверить подписанное удаление источника/локального участника/
поставщика, закрытие соответствующих handles и отсутствие выдачи новых DATA.
После исправления продолжить сверку оставшихся заголовков и сетевых контрактов.
Реализация не исправлялась: аудит остановлен по правилу AGENTS.md:10.
2. Выполненная сверка
Все 18 заголовков src/stream прочитаны предварительно. Полностью прочитаны
10 реализаций: stream_block/source/buffer/media/media_buffer/wire/member/metrics/
load/playback_clock. Другие реализации stream, включая playback_audio.cpp,
network/optimizer/publisher/viewer и stream.c целиком, ещё не проверены.
В stream.c просмотрена только затронутая ветка member_changed; полный аудит
жизненного цикла сервиса этим не заявляется.
Завершены и готовы к [V]: stream_proto.h, stream_block.h, stream_source.h,
stream_media.h. Исправлены описание полного размера части и области подписи,
предусловия BE helpers, Ed25519 seed/ключ, владение и ошибки codec/source,
нулевой write/accepted, отсутствие END в source_finish, ограничения uAV2,
пересчёт first_frame_offset и атомарность views/выходов при ошибке.
В этих четырёх заголовках изменены комментарии/оформление, C-токены сохранены.
Частично проверены [~]: stream_buffer.h, stream_load.h, stream_media_buffer.h,
stream_member.h, stream_metrics.h, stream_playback_clock.h, stream_wire.h.
Их реализации прочитаны, но оставшиеся уточнения контрактов не внесены после
остановки. В stream_member.h добавлено предупреждение о подтверждённом дефекте.
Остальные семь заголовков остаются [ ]: stream.h, stream_network.h,
stream_optimizer.h, stream_playback_audio.h, stream_priv.h, stream_publisher.h,
stream_viewer.h. Прочтение одного заголовка без реализации не закрывает аудит.
doc/stream.txt остаётся [~]: просмотрены начальные разделы о FEC, подписи,
кольце и выдачах; последующие сетевые/media/desktop/Android разделы не сверены.
Добавлен статус аудита.
Полные .h/doc контракты всего модуля ещё не закрыты.
3. Проверки и ограничения
Сборка диагностического стенда PASS; отрицательная проверка deleted воспроизвела дефект.
Профильные наборы stream, полный check.sh, санитайзеры, Windows/FreeBSD и
GUI/Android runtime не запускались. Неизменность C-токенов заголовков и
git diff --check — PASS; успешные сетевые/медиа тесты не заявляются.
Предыдущие изменения Local_transport и чужие записи чек-листа сохранены.
4. Архитектурная проверка предлагаемого исправления — 26.10.2026
Вывод: проверка deleted в stream_member_load архитектурно корректна. На этом
этапе проверка выполнялась на временной копии модуля без изменений .c проекта;
внедрение описано в разделе 5. Статусы полного аудита каталога этим не закрываются.
Граница ответственности
Блок A участника описывает личность/stream-опции. Блок B владельца определяет
в том числе исключение из канала. Корректный A не отменяет запрет из B.
member_apply_record проверяет подпись B и его версию; owner_put обновляет
adm_tags/adm_tags_sig/storage/deleted одним UPDATE. После успешного commit
member_committed посылает props-событие; Merkle-page уведомляет после commit
всей страницы. Поэтому достаточно прочитать принятую проекцию deleted в том же
SELECT, не вводя вторую криптопроверку блока B или отдельный SQL-запрос допуска.
STREAM_ERR_NOT_MEMBER уже обработан в stream_open_check, каталоге источников,
member_changed/service_reconcile, authorized и network_membership_changed.
Событие источника/self немедленно ставит invalidated, затем reconcile закрывает
сессию. session_find/handle_active/session_deliver учитывают invalidated до
отложенного освобождения. Закрытие сохраняет владение handles у caller до close.
Для исключённого поставщика закрываются его flow, а локальная сессия источника
может продолжать получать от другого поставщика. Сфера запрета — конкретный
канал; общий ETCP/NCD и владельцы других каналов не должны закрываться.
Результаты реального стенда
В /tmp/stream-arch-member.c добавлены только поле deleted в существующий SELECT
и ветка NOT_MEMBER с причиной в логе до разбора профиля. Копия связана перед
штатной libutun.a, остальные модули взяты из существующей сборки.
Стенд использует fixture test_stream_service: четыре живых ядра, два канала,
подписанные owner/member записи, настоящий Merkle, UDP/TCP и NCD.
Все четыре группы проверок PASS:
- Неполный профиль/placeholder остаётся WAIT; отсутствие и deleted дают
NOT_MEMBER; state/public_key не меняются на отказе. Неверная подпись владельца
отвергается, старая версия не снимает запрет, более свежая разрешает участие.
- Исключение источника сразу закрывает receive с MEMBER и инвалидирует reader;
новых DATA callback нет. Запрет через Merkle останавливает сам publisher.
Handle другого канала и транспорт с дополнительным NCD-владельцем сохраняются.
- Исключение поставщика закрывает только его локальную выдачу; reader источника,
другой канал и upstream другого ядра в этот момент остаются активными.
- Исключение self закрывает handles данного канала и запрещает новые publish/
subscribe. Другой канал остаётся активен. После разрешения новый open работает,
прежние отсоединённые handles не оживают. Новая публикация получает новую эпоху.
Итоговый exit=0, teardown flows=0 и ноль оставшихся tracked allocations.
Сборка: /tmp/stream-arch.mk, /tmp/stream-arch-build-final.log.
Кейсы: /tmp/stream-arch-cases.h; harness: /tmp/stream-arch-service.c.
Результат: /tmp/stream-arch-results-final.log.
Запуск: /tmp/stream-arch-service --architecture-only, вне sandbox.
Первый прогон /tmp/stream-arch-results.log неверно пытался повторно использовать
publisher после распространённого через Merkle запрета: старый publisher уже
правильно закрыт. Ожидание стенда исправлено на новый запуск после разрешения,
сама временная проверка deleted при повторе не менялась.
Гарантии и ограничения
Уведомления/освобождение остаются отложенными; нужен обычный uasync loop.
Проверено отсутствие новых DATA callback у исключённого reader. Уже принятые
ETCP пакеты не отзываются, удалённые узлы узнают запрет при его доставке через
Merkle. Архитектурная проверка не обещает мгновенного глобального отзыва данных.
Полный check.sh, санитайзеры и платформенные/GUI runtime не запускались.
На этапе архитектурной проверки C-код репозитория не менялся; последующее
внедрение и постоянная регрессия описаны ниже.
5. Внедрение согласованного исправления — 26.10.2026
В stream_member_load существующий SELECT дополнен deleted. Для удалённой записи
до crypto возвращается STREAM_ERR_NOT_MEMBER, с отдельным DEBUG_WARN о запрете
владельца, group_id/member_id. Другого SQL-запроса, нового API или формата нет.
Остальные результаты loader, включая WAIT, и атомарность выходов сохранены.
Предупреждение о неисправленном дефекте в stream_member.h заменено действующим
контрактом. Задача deleted в doc/tasks.md закрыта, текущий статус stream.txt уточнён.
Постоянная регрессия — test_stream_member в check_PROGRAMS. Тест использует
test_stream_service.c с теми же четырьмя ядрами/подписанными записями/сетью,
а STREAM_MEMBER_TEST_ONLY выбирает изолированный сценарий в отдельном процессе.
Fixture не дублируется, её изменённые member-версии/активность не влияют на
обычный test_stream_service. Сценарий доступен также через --member-only.
Проверены подписи/версии владельца, WAIT/NOT_MEMBER и сохранность выходов,
удаление источника/поставщика/self, причины закрытия, отсутствие новых DATA
callback после запрета, распространение через Merkle до остановки publisher,
сохранность чужого NCD-владения и работоспособность другого канала после
исключения self. Разрешение не оживляет старые handles; новая публикация
проверяется на новую эпоху. Ожидание удалённой остановки зависит от состояния,
а не фиксированного sleep; teardown подтверждает отсутствие tracked allocations.
./build.sh --no-gui -j4 PASS: /tmp/stream-member-fix-build.log.
Профиль7/7 PASS: stream_member, stream_service, stream_core, stream_media,
stream_playback, stream_tx_stats, android_stream.
Сводка /tmp/stream-member-fix-profile-results.log,
подробности /tmp/stream-member-fix-test_*.log.
Последующий перенос длинной строки лога не меняет содержимое сообщения;
обе test-stream цели пересобраны, test_stream_member повторно PASS:
/tmp/stream-member-fix-test-rebuild.log, /tmp/stream-member-fix-test_stream_member-final.log.
Полный Linux check.sh:195/195 PASS,0 failed/0 skipped; proxy9/9,burst PASS,
load8/8,134217728 байт,167 секунд. Лог /tmp/stream-member-fix-full-check.log.
git diff --check PASS. Sanitizers и нативные Windows/FreeBSD/GUI/Android runtime
в этом этапе не запускались; test_android_stream — тест общего C bridge на Linux.
Отметки полного аудита заголовков не расширены: устранение одного дефекта
не завершает оставшуюся сверку src/stream и всего doc/stream.txt.
6. Продолжение аудита: помещающийся Opus не упаковывается — 26.10.2026
Полностью прочитаны stream_playback_audio.cpp, stream_viewer.c, stream_publisher.c
и их заголовки. Всего прочитаны13 реализаций; полные stream.c/network/optimizer
пока не сверены. До исправления документов трёх новых модулей подтверждён дефект,
поэтому publisher.h/viewer.h/playback_audio.h отмечены[~], не[V].
Теперь4 заголовка[V],10[~],4[ ]; doc/stream.txt[~] сохраняется.
Причина
В publisher_block (stream_publisher.c:145) перед каждым Opus проверяется
used + STREAM_MEDIA_AUDIO_SIZE + packet_length + STREAM_MEDIA_VIDEO_SIZE >= capacity.
Резерв video descriptor добавляется всегда, включая полностью пустую video-очередь.
В результате допустимая audio-only запись, которая целиком помещается в uAV2,
не попадает в block. При отсутствии видео publisher_block возвращает WAIT,
actor повторяет попытки и после MEDIA_AGE_US=250000 удаляет принятый пакет.
Штатный лог говорит audio expired, не объясняя блокировку из-за лишнего резерва.
Фактическое воспроизведение
Один настоящий локальный publisher, отдельный автоматически созданный канал,
SQLite в памяти, stream_subblocks=1/stream_rs_redundant=0. Ёмкость содержимого
FEC-пачки1125 байт. Видео в очереди нет. Codec Opus48kHz stereo/20мс:
штатный opus_codec_encode плюс opus_packet_pad; оба тестовых пакета дают
opus_packet_get_nb_samples=960 и действительно декодируются в960 frames/channel.
Самостоятельный stream_media_encode успешно помещает оба блока в ту же ёмкость.
После publisher feed принимаются настоящие RS-пачки, проверяется подпись,
содержимое декодируется штатным stream_media_parse в DATA callback.
packet=1044 valid_samples=960 media_bytes=1092 capacity=1125 feed=1 delivered=1 queued=0 state=LIVE
packet=1045 valid_samples=960 media_bytes=1093 capacity=1125 feed=1 delivered=0 queued=0 state=LIVE
В логе между этими строками: publisher audio expired pts=1020000.
Результат: expected=2 received=1, exit=1; после destroy нет оставшихся tracked allocations.
Для каждой проверки отсчитано500мс: больше TTL и периода обновления TX snapshot.
Это не переполнение TX, не испорченный Opus и не нехватка места в полной пачке.
Стенд: /tmp/stream-audit-audio-pack.c, дополнительная цель /tmp/stream-audit-audio-pack.mk.
Сборка через штатные Makefile/LIBUTUN/COMMON_LIBS/test_clock.o PASS:
/tmp/stream-audit-audio-pack-build.log. Полный лог /tmp/stream-audit-audio-pack.log.
Первый диагностический запуск остановился на неверной константе capacity1133
в стенде; она заменена вычислением границы из фактической ёмкости1125.
Реализация проекта при диагностике не менялась.
Предлагаемый вариант исправления
Упаковка должна учитывать фактически добавляемые описания/данные и допускать
audio-only пачку, если целый Opus в неё помещается. Резерв под видео не должен
навсегда блокировать принятую audio-запись. Простой вариант — заполнять audio
до его реальной ёмкости, затем видео в оставшееся место; нехватка места под видео
означает следующую пачку, а не потерю audio. Wire и неделимость Opus сохраняются.
Если важно гарантировать часть видео в каждой смешанной пачке, нужен явный
переход к отдельной audio-only пачке для такой головы очереди; это более сложный вариант.
Проверить1044/1045 байтов, максимальное audio-only содержимое1077 для FEC1+0,
точное заполнение capacity, очередь видео/пустую очередь и прежний default8+0.
Отдельно различать запись, которая вообще не помещается в доступную FEC-пачку,
и запись, не поместившуюся только в текущий остаток: диагностика должна показывать
packet_length, остаток/полную ёмкость и причину, не маскируя всё как expiry.
Реализация не исправлялась; дальнейший аудит остановлен по AGENTS.md:10.
В publisher.h добавлено предупреждение, задача открыта в doc/tasks.md.
Новый полный build/check, sanitizers и Qt/Android runtime в этом продолжении
не запускались; прежний check195/195 из раздела5 новый дефект не покрывает.
Оставшиеся уточнения новых заголовков (не выполнены после остановки):
- playback_audio: create/destroy lifetime, producer/consumer дисциплина всех getters,
потребление через needs_input, feed0/1/-1/-2, PLC до100мс, prepare после finish,
EOF в empty означает извлечённый PCM, а не проигранный аппаратурой звук.
- viewer: единый callback manager, notify без reentry, асинхронный stop до освобождения ID,
пределы demand/video<=records/audio_ahead, нулевой снимок status до открытия,
уничтожение manager требует остановки callers и живого instance.
- publisher: чтение настроек FEC при actor-open, snapshot смешивает локальные очереди
текущего источника и общие ETCP-счётчики ядра, PTS/revision/KEY admission,
ошибки/состояния и условное сохранение кольца локальным readers после FIN.
7. Исправление упаковки и продвижения треков — 26.10.2026
Принят локальный вариант без изменения wire, публичного API и неделимости Opus.
Без ожидающего видео рассчитывается только фактическая стоимость audio: header32,
descriptor16, payload. Условие допуска допускает равенство полной ёмкости.
Для обычной смешанной пачки сохранён резерв descriptor32 и хотя бы байта видео.
Если первая audio-запись помещается только отдельно, публикуется audio-only пачка.
Локальный video_turn отдаёт второй слот того же actor видео; coalescing20ms в этом
слоте не применяется. Между actor-вызовами состояние не добавлено; лимит2 пачки,
mutex, порядок PTS и смещения фрагментов сохраняются. Публикуемые записи расходуются
только после успешных encode/publish; очередь при ошибке закрывается прежним путём.
Opus длиннее capacity-header32-descriptor16 actor удаляет с WARN, содержащим ID,
PTS, bytes, max и capacity. Это окончательный отказ конкретной записи, не WAIT
до истечения TTL. Следующая запись продолжает обрабатываться. Проверка находится
в actor: во время OPENING producer ещё не знает выбранную FEC-ёмкость.
feed=1 означает копирование в ограниченную очередь; асинхронный отказ и TTL
оговорены в publisher.h и stream.txt. Удаление ошибочной записи не меняет video KEY.
DEBUG показывает успешную отдельную аудиопачку и отложенное видео, TRACE — состав
каждой опубликованной пачки; WARN сообщает точную причину невозможности упаковки.
Практический масштаб
Воспроизведение использует валидный Opus, искусственно увеличенный opus_packet_pad.
Обычный desktop/Android encoder настроен AUDIO128kbit/s/20ms; средний бюджет320 байт
не является пределом VBR. Потеря обычного естественно закодированного звука не
подтверждена. При default8+0 capacity9833 одиночный Opus<=1275 помещается.
Ёмкость1125 относится ко всем конфигурациям с n-r=1 при штатном RS-size, включая2/1.
Проверки
Временная копия и реальный publisher/RS/uAV2:21/21 PASS, без tracked leaks;
/tmp/stream-plan-check.log. Старый бинарник проваливает границу1045:
/tmp/stream-plan-baseline.log. Исходники проекта при этой проверке плана не менялись.
Штатный tests/test_stream_publisher.c и цель tests/Makefile.am:23/23 PASS.
Границы1044/1045/1077/1078/1275, точная пачка из двух audio-записей, раздельные
пачки, крупная audio-очередь32 записи, unfit в начале/середине/подряд, video-only,
mixed и продолжения видео, OPENING, Stop/FIN, n/r1/0,2/1,2/0,8/0, лимит8 Opus.
Callback проверяет фактические PTS/длины, декодирует Opus960 frames/channel,
проверяет каждый байт/offset видео. Все ожидаемые данные получены до TTL250ms.
На Linux wrap uasync_set_timeout запрещает publisher_media timer после крупного
Opus и короткого P-кадра: оба должны публиковаться в том же actor. Проверка не
сравнивает задержки процессора с порогом5ms. Cleanup каждого ядра восстанавливает
tracked allocation count. Другие платформы сохраняют проверки содержимого/порядка.
./build.sh --no-gui -j4 PASS: /tmp/stream-publisher-fix-build.log.
./tests/test_stream_publisher PASS: /tmp/stream-publisher-fix-regression.log.
Перед итоговой проверкой origin/master8e746195 объединён без конфликтов в ced9dc23.
Повторная ./build.sh --no-gui -j4 PASS: /tmp/stream-publisher-fix-merged-build.log.
Профиль8/8 PASS: stream_publisher,core,media,playback,tx_stats,android_stream,service,member;
/tmp/stream-publisher-fix-profile-results.log и /tmp/stream-publisher-fix-test_*.log.
Полный ./check.sh PASS:200 passed,0 failed,0 skipped; новый publisher-тест764ms.
Proxy9/9,burst PASS,load8/8,134217728 байт; полное время175с.
Лог /tmp/stream-publisher-fix-full-check.log. git diff --check PASS.
Sanitizers, Qt GUI и Android на устройстве в этом исправлении не запускались.
Отметки аудита4[V]/10[~]/4[ ] не расширяются исправлением кода.
8. Аудит stream.c/optimizer и просроченный INFO_REQ — 26.10.2026
Полностью прочитаны stream.c1249 строк и stream_optimizer.c712 строк; теперь
15 из16 реализаций прочитаны полностью. В stream_network.c прочитаны строки1–1615;
оставшаяся часть, перегрузка/relay и общая сверка network ещё не завершены.
Уточнены stream.h и stream_optimizer.h: общая сессия/позиция raw событий,
независимость media cursors, detached ownership, FIN, режим manual/automatic,
условия ACTIVE и mobile-supernode, параметры/оценки/сроки и recovery selector.
Публичные структуры переформатированы с сохранением полей/порядка/типов.
stream.h и optimizer.h отмечены[V]; network.h и priv.h остаются частичными[~].
Итог6[V]/12[~]/0[ ]; doc/stream.txt[~]. Все остальные отметки сохранены.
Подтверждённое расхождение
prepare_requests формирует uint8_t probe[9]: probe_id8 + media_flag1.
Raw dispatch тоже требует INFO_REQ length9. В expire_controls (stream_network.c:1147)
для поиска связанного flow ошибочно оставлена проверка m.length != 8.
Поэтому любой штатный INFO_REQ, просроченный в управляющей FIFO, удаляется из
очереди после4000мс, но соответствующий flow не закрывается с OVERLOAD.
Его info_sent остаётся1, info_ready=0, state=INFO. Повтор INFO не ставится.
По network_tick он ждёт общий deadline5000мс и получает TRANSPORT; selector
обрабатывает такой результат иначе, включая peer backoff. Полный цикл
NCD/ETCP/selector и величина последующего backoff в этом воспроизведении не измерялись.
Диагностика
Стенд /tmp/stream-audit-info-expiry.c включает настоящую реализацию network,
использует uasync/FIFO, control_put, stream_wire_parse и expire_controls.
Это изолированная проверка очереди/flow без запуска сетевых подключений:
время передаётся непосредственно как queued_ms + TTL, не моделируется потеря реального ETCP.
До/на TTL4000мс все записи сохраняются; после4001мс:
INFO_REQ9 media0: queue0/stateINFO/reason0, ожидалось CLOSED/OVERLOAD — FAIL.
INFO_REQ9 media1: queue0/stateINFO/reason0, ожидалось CLOSED/OVERLOAD — FAIL.
INFO_REQ9 с прежним probe_id: queue0/stateINFO — PASS, новую пробу закрывать нельзя.
SUBSCRIBE8: queue0/stateCLOSED/reasonOVERLOAD — PASS.
Итог2 failed из4, exit1, после cleanup tracked allocations восстановлены.
Сборка через штатный Makefile/LIBUTUN/COMMON_LIBS и дополнение
/tmp/stream-audit-info-expiry.mk PASS; лог /tmp/stream-audit-info-expiry-build.log.
Runtime: /tmp/stream-audit-info-expiry.log. Включены stream=debug, video=warn,
debug=info; видно удаление control и отсутствие закрытия INFO-flow.
Временная копия /tmp/stream-network-info-expiry-checked.c отличается только
сверкой length9 вместо8. На ней тот же стенд4/4 PASS, exit0, tracked allocations
восстановлены: /tmp/stream-audit-info-expiry-checked-build.log и ...-checked.log.
C/C++ реализации проекта не менялись; исправлены только описания.
Предлагаемое исправление
Сверять полный INFO_REQ9 (ID8 + media flag1), оставив проверку конкретного probe_id.
Просроченная проба должна закрываться существующим flow_close(OVERLOAD,notify0);
новые состояния, протокол или дополнительный таймер не нужны. Старый probe_id
не должен закрывать новую попытку. Закрепить обе media-формы, SUBSCRIBE8 и границы
TTL отдельным штатным regression; дополнительно удержать настоящую ETCP-очередь
дольше4с и проверить CLOSED/OVERLOAD до общего connect deadline5с.
Аудит остановлен по AGENTS.md:10 до изменения реализации. Задача добавлена
в doc/tasks.md; текущий статус отражён в stream.txt и внешнем чек-листе.
Полная сборка/check и sanitizers в этом продолжении не запускались; прежний
check200/200 относится к исправлению publisher и новый дефект не покрывает.
9. Исправление INFO expiry и проверка ETCP backpressure — 26.10.2026
expire_controls теперь сопоставляет полный INFO_REQ9 (ID8 + media1) с текущей
пробой. Проверка probe_id сохранена; истёкший запрос закрывает flow через прежний
flow_close(OVERLOAD,notify0). Wire, состояния и таймеры не менялись.
Существующий WARN дополнен bytes/age_ms/flow_closed: видно удаление конкретного
control и наличие связанного закрытия. Код исправления c1483512, тесты b116b20b.
Во время работы история перенесена поверх GUI-изменений; отсутствие различий
в src/lib/tests относительно собранного d89c1efa проверено отдельно.
Штатный test_stream_control_expiry включён в общий check и напрямую использует
реальные control_put/wire parser/expire_controls. Проверены границы3999/4000/4001мс:
INFO9 media0/1 после TTL закрывают flow с OVERLOAD, несовпадающий probe_id
не закрывает текущую попытку, SUBSCRIBE8 сохраняет прежнюю обработку.
4/4 PASS; отслеживаемые allocations после cleanup восстановлены.
В test_stream_service добавлена проверка на четырёх настоящих CHAT-ядрах с Merkle,
NCD и живым ETCP. Она выполняется в общем наборе и отдельно через --control-expiry-only.
После прогрева NCD/INFO callback настоящей send_input_q временно снимается.
Первый INFO остаётся в ETCP, второй — в control FIFO. Через4200мс в обоих media-режимах:
expired_sub0 state=CLOSED reason=OVERLOAD; held_sub1 state=INFO; publication/subscription
активны, исходное ETCP-соединение initialized/links_up. После возврата callback
приходит INFO_RESP; просроченная проба переоткрывается и получает новый ответ через
то же ETCP-соединение. Cleanup всех четырёх ядер восстанавливает tracked allocations.
В общем check этот же сценарий также прошёл внутри полного test_stream_service.
Проверки и условия среды
./build.sh --no-gui -j4 PASS: /tmp/stream-info-expiry-fix-build.log.
test_stream_control_expiry4/4 PASS: /tmp/stream-info-expiry-fix-unit.log.
test_stream_service --control-expiry-only PASS: /tmp/stream-info-expiry-fix-integration.log.
В изолированном сетевом прогоне включён stream=debug; unit включает video=warn,
поэтому проверен также новый WARN с bytes9/flow_closed1 и несовпадением ID/flow_closed0.
В fixture остаются логи отрицательных reachability-проб TCP; потоковые asserts,
готовность транспорта, восстановление и счётчики ресурсов проверены отдельно.
Первый ./check.sh:198 passed,3 failed,0 skipped. Все три отказа вызваны bind(::1):
EADDRNOTAVAIL в test_ncd_faults, test_ipv6_sockets, test_route_connectivity.
IPv6 в основном namespace отключён (all/default/lo=1), ::1 отсутствует,
bind(::) работает. Сводка /tmp/stream-info-expiry-fix-full-check.log;
состояние среды /tmp/stream-info-expiry-host-ipv6.log. Код из-за этих отказов не менялся.
Создан отдельный временный network namespace; IPv6 включён только в нём,
поднят lo/::1, сами проверки выполнялись от user. Все три прежних отказа PASS,
затем ./check.sh:201 passed,0 failed,0 skipped. Новый unit32мс, service80173мс.
Proxy9/9,burst PASS,load8/8,134217728 байт; полное время175с.
Логи /tmp/stream-info-expiry-fix-ipv6-env.log,
/tmp/stream-info-expiry-fix-ipv6-full-check.log и /tmp/stream-info-expiry-ipv6-test_*.log.
Основной namespace сохранён: all/default/lo=1 и bind(::1) по-прежнему EADDRNOTAVAIL.
Необходимость явной IPv6-диагностики runner записана отдельной задачей в doc/tasks.md.
git diff --check PASS. Sanitizers, Qt GUI и Android на устройстве в этом исправлении
не запускались. Устранение дефекта не расширяет аудит:6[V]/12[~]/0[ ],doc/stream.txt[~].
10. Завершение сверки заголовков общего stream-ядра — 26.10.2026
Дочитан stream_network.c1616–2185: relay lifecycle, dispatch, общий tick,
перегрузка нескольких пиров, учёт кольца, позиции получателей и телеметрия.
Вместе с предыдущими этапами полностью прочитаны все15 C-реализаций и
stream_playback_audio.cpp (всего7775 строк), все18 заголовков.
Для уточнения контрактов повторно прочитаны полные buffer/media_buffer,
member/wire, load/metrics, playback_clock/audio, publisher/viewer.
Уточнения заголовков
- network/priv: принадлежность caller-owned handles после detach, manual/internal
ограничения, INFO nonce/повторы/сроки, аренда6с и RENEW2с, control TTL>4с,
пределы общих бюджетов, готовая INFO-проба без автоматического close,
первый DATA как структурная часть до проверки подписи, диапазоны/RTT/validity,
restart с сохранением consumer floor, relay READY/BOUND и сроки помощи.
- load/metrics: A/D/X/Q, атомарность add/remove, revision и окна роста/rates,
live-счётчики против последнего снимка rates, rebase и 16-битный timestamp,
первая пара для rate, строгий порог остановки, END и старение истории.
- buffer/media_buffer: размер слотов против фактических wire-байтов,
заимствованные данные/observer, поиск частей против проверки подписи,
неизменяемый набор k после decode, неподписанные timing/telemetry,
независимые позиции треков, generation/GAP/revision, status до KEY,
bounded MORE/WAIT, audio lookahead и продолжение последнего кадра после finish.
- member/wire: parse/format против доверенного load, BGP отдельно от членства,
собственный online, роль deleted=0, переключатель audio против codec эпохи,
конкретные размеры/предусловия writers/readers, путь, телеметрия и подпись END.
- playback_clock/audio: состояние регулятора/история и часы, допустимые входы,
producer/consumer и внешняя блокировка, 20-мс stereo output, PLC/разрыв,
needs_input/prepare/finish, hardware frame origin, empty до опустошения устройства.
- publisher/viewer: асинхронное открытие/stop, время занятия session ID,
KEY/revision/admission/TTL, feed как принятие очередью, mute без смены codec,
общие ETCP-счётчики instance, notify под lock/исключительный destroy,
владение батчем, диапазоны demand и нулевые снимки до обновления actor.
Последние изменения этих12 заголовков — комментарии/оформление. Сравнение
лексических C-токенов с сохранённой до правок базой проверяет имена, типы,
порядок полей, объявления и выражения; все12 совпали. Публичный API/ABI,
состояния, протокол и C/C++ логика в данном продолжении не менялись.
Новых подтверждённых дефектов реализации в этой части аудита не найдено.
В stream.txt согласованы native-разделы: текущая версия wire7, INFO_REQ9,
сроки и закрытие control, совместимость через NCD без собственного
route_connectivity probe-движка, структурная часть против подписанной пачки,
60-секундные rates, recovery без сброса media и отсутствие пропуска по одному
возрасту DATA. Уточнено, что relay может готовить media-индекс по want_media.
Старые результаты wire6/utun2 помечены историческими.
Покрытие:18[V]/0[~]/0[ ] в src/stream. doc/stream.txt[~]: ещё нужны
полная сверка desktop/Android capture/decoder/audio/UI и внешнего
tests/stream_netns, их аппаратные и старые ASAN-результаты здесь не перепроверены.
Завершение заголовков не объявляет эти внешние компоненты проверенными.
Проверка ./build.sh --no-gui -j4 PASS: /tmp/stream-header-audit-build.log.
Полный ./check.sh PASS:201 passed,0 failed,0 skipped; proxy9/9,burst PASS,
load8/8,134217728 байт. Полное время172с. Все9 профильных core/media/playback/
tx_stats/Android bridge/service/member/publisher/control_expiry наборов PASS.
Лог /tmp/stream-header-audit-check.log; условия /tmp/stream-header-audit-netns.log.
Прогон выполнен от user в отдельном network namespace с IPv6/lo/::1;
основной namespace сохранён (all/default/lo.disable_ipv6=1).
Неизменность C-токенов12/12, отсутствие правок C/C++ реализаций/lib/tests
относительно5259c746, строки<=150, diff --check и отметки чек-листа PASS:
/tmp/stream-header-audit-contract-check.log. После прогона менялись лишь
два комментария (Ed25519 public key и ненулевая база expected interval);
лексические токены проверены повторно, новый прогон не требовался.
Sanitizers, отдельные Qt/C++ audio tests, desktop GUI и Android на устройстве
в этом продолжении не запускались. Новых runtime-проверок многопроцессного
tests/stream_netns не было; тестовый namespace здесь служил только средой check.sh.