Файлы участников CHAT-группы =========================== Модуль: src/member_files/. Desktop-браузер: tools/chatgui/src/member_files/. Отдельный сервис router ETCP_RT_ID_MEMBER_FILES (0x39), отдельно от вложений сообщений media_delivery и E2E-файлов DM. Серверы выбираются явно; между серверными деревьями автоматической репликации нет. Хранение и конфигурация ---------------------- Корень по умолчанию: /member_files/ (без db_path — ./member_files/). Публичное дерево: <корень>///<относительный путь>. Временные файлы: <корень>/.uploads/. При старте сервис создаёт/открывает корень в дисковом worker, удаляет старые незавершённые temp и сканирует опубликованные файлы. До готовности отвечает BUSY. Ошибка сканирования оставляет локальное хранилище недоступным до restart; частичные счётчики клиенту не выдаются. Клиентские передачи к другим серверам продолжают работать. Очистка кэша вложений media/ это дерево не обходит. Настройки [chatserver] / [chat]: member_files_enabled=1 member_files_user_quota_mb=1024 member_files_upload_ttl_sec=3600 member_files_path=/путь/к/хранилищу (опционально, до 255 байт UTF-8) Путь, квота и TTL читаются при старте чат-сервиса; изменение требует его restart. enabled проверяется на каждом серверном запросе; клиент доступа к другим storage остаётся доступен. Лимит одинаков для каждого UID отдельно, размер UID суммируется по всем группам на этом сервере. Значение в МиБ (1024*1024 байт). Счётчики used/reserved/files/directories хранятся в памяти дискового worker и восстанавливаются с диска только при старте. Каталоги не занимают байтовую квоту. files/directories считают опубликованные файлы и каталоги внутри деревьев UID по всем группам; корни групп, корни UID и .uploads исключены. Пустой опубликованный файл считается файлом. Сохранённые файлы удалённых участников также занимают квоту. При начале загрузки резервируется размер нового файла или положительный прирост при замене. WRITE не изменяет used; COMMIT обновляет used на разницу размеров и освобождает резерв. ABORT/Cancel/TTL освобождают резерв после удаления temp; REMOVE вычитает размер файла. Переименование внутри UID не меняет размер и числа объектов. Новый COMMIT увеличивает files на 1; замена существующего файла, включая нулевой размер, сохраняет files. MKDIR увеличивает directories на 1, REMOVE уменьшает соответствующий счётчик. Для mkdir учитывается факт создания даже при последующей ошибке sync. BEGIN/WRITE/ABORT/Cancel/TTL не увеличивают files: temp не опубликован. Все изменения корректируют счётчики инкрементально, без обхода каталога пользователя; QUOTA только читает их из памяти. LIST выполняет свой обычный обход для отображения записей, не пересчитывая статистику. Повторы BEGIN/COMMIT/MKDIR/REMOVE не меняют учёт повторно. Уже превышенная квота не удаляет данные: чтение, удаление и уменьшение файла доступны, новый положительный прирост запрещён. Физический temp может занимать больше места, чем резерв прироста; нехватка места файловой системы остаётся отдельной I/O-ошибкой. Нехватка квоты/места возвращает NO_SPACE; опубликованные файлы не вытесняются. Общая member_files_quota_mb удалена. Корень обслуживается только файловым сервисом; ручные изменения требуют повторного сканирования через restart. Сервер — действующий полный мембер группы с подписанным adm_tags.storage="yes". Колонка peers.storage — производный сетевой приоритет, для полномочий не используется. Каждый запрос и ответ подписаны Ed25519 и зашифрованы end-to-end средствами route_crypto. group/src берутся только из проверенного заголовка router; ETCP peer промежуточного хопа не считается автором запроса. Читать могут действующие участники той же группы. Изменять дерево может только src == owner. Удалённые записи и source=1 (плейсхолдеры) доступа не дают. Открытый fd/UUID загрузки не отменяют повторную проверку членства. Файлы удалённого участника сохраняются на диске, но через сервис не доступны. Wire, версия 4 -------------- Все числа LE, без сырых структур и padding. Фиксированный заголовок 125 байт: 0 protocol_version:u8 = 4 1 kind:u8 (1=request, 2=response) 2 command:u8 3 status:u16 (request=0) 5 request_id:u64, ненулевой случайный ID 13 owner:u64 (UID каталога; 0 только для LIST виртуального корня группы) 21 offset:u64 29 size:u64 37 cursor:u64 45 version:16 61 upload:16 77 hash:32 109 mtime:u64, Unix seconds 117 type:u8 (1=file, 2=directory) 118 more:u8 (0/1) 119 path_len:u16 121 target_len:u16 123 data_len:u16 После заголовка: path[path_len], target[target_len], data[data_len], без NUL. Длина пакета обязана точно совпадать с указанными размерами. Путь максимум 1024 байта, компонент максимум 255 байт, максимум 60 компонентов. Данные максимум 8192 байт; вместе с полным router-путём и crypto укладываются в лимит normalizer 16384 байта. Размер файла/смещение ограничены INT64_MAX. Пути — строгий UTF-8, относительные, разделитель '/'. Запрещены пустые компоненты, компоненты с ведущей '.', завершающей точкой/пробелом, ASCII C0/DEL, Windows device names и символы \ : * ? " < > |. Корень внутри UID — пустой путь. POSIX разрешает путь через *at/O_NOFOLLOW; regular-файлы с hardlink отвергаются. Windows удерживает родительские каталоги без SHARE_DELETE и не принимает reparse points. Команды --------------- 1 LIST: owner/path/cursor/version. Первая страница cursor=0, version=0. Ответ data — записи: name_len:u16,type:u8,size:u64,mtime:u64,version:16,name. cursor — сколько записей уже выдано. more=1 требует следующую страницу с возвращёнными cursor/version. При изменении снимка — STALE, начать сначала. Размер каталога не ограничивает память страницы. Виртуальный корень группы выдаёт UID действующих полных участников, даже если каталог ещё не создан. Изменение namespace сервера консервативно инвалидирует дисковые LIST-курсоры. 2 STAT: owner/path. Ответ type/size/mtime/version. Не хеширует весь файл. version — непрозрачный снимок identity/size/mtime/ctime и эпохи сервера. Закрытие fd по таймауту токен не меняет; restart меняет дисковые токены. 3 READ: owner/path/version/offset/size (1..8192). Ответ size — полный размер файла, data_len — фактически прочитано, more — есть ли байты после ответа. offset==EOF возвращает 0 байт/more=0; offset>EOF — INVALID_RANGE. Изменение файла между блоками — STALE. Клиент не смешивает версии и публикует локальный файл только по завершении. 4 BEGIN_WRITE: owner/path/upload/size/hash/version. upload — 16 случайных байт, size — конечный размер, hash — SHA256 содержимого. version=0 означает создать новый файл, существующий возвращает EXISTS. Для замены требуется версия из STAT/LIST. В один путь одна активная загрузка. UUID привязан к group/src/owner/path; повтор BEGIN с теми же параметрами безопасен. 5 WRITE: owner/path/upload/offset/size/data. size==data_len, диапазон внутри конечного размера. Можно писать в произвольном порядке и повторять блок. ACK подтверждает запись в temp, а не durable публикацию всего файла. 6 COMMIT: owner/path/upload. Проверяет SHA256, fsync/_commit и ожидаемую версию назначения, затем атомарно заменяет файл и синхронизирует namespace. Прежний файл виден до публикации. Ответ содержит размер/версию/SHA256. Незаписанные sparse-области читаются как нули; неправильное содержимое не проходит SHA256. Пустые файлы поддерживаются. При I/O-ошибке во время публикации результат может быть неопределённым; нужно повторно проверить STAT. 7 ABORT: owner/path/upload. Удаляет temp, освобождает резервацию. Неизвестный UUID — OK; опубликованные файлы никогда не удаляет. 8 MKDIR: owner/path. Создаёт один каталог; родители должны существовать. 9 RENAME: owner/path/target/version. Только внутри UID/группы, без замены существующего назначения. Активная загрузка в затрагиваемом пути — BUSY. 10 REMOVE: owner/path/version. Файл или пустой каталог, без рекурсивного удаления. Непустой каталог возвращает DIRECTORY_NOT_EMPTY. 11 STREAM_WRITE: открытие потоковой загрузки; поля как BEGIN_WRITE, cursor — ненулевой ID потока. Ответ подтверждает готовность temp и конечный размер. 12 STREAM_READ: открытие потокового скачивания; owner/path/version/size/cursor, upload — случайный токен клиента. Сервер сверяет снимок и размер перед началом. 13 STREAM_DATA: request_id — ID потока, owner/path/upload, offset/size/data. kind=request для upload, response для download. Ответа на блок нет. offset обязан равняться следующему принятому смещению; size==data_len, не более 8192. 14 STREAM_END: ID потока, owner/path/upload, offset==size==размер файла, SHA256. Upload: клиент отправляет END после всех DATA, сервер отвечает END после COMMIT. Download: сервер отправляет END после DATA и повторной проверки версии; клиент проверяет SHA256 записанных данных, синхронизирует и публикует локальный файл. Ошибка сервера приходит END.status!=OK; ошибка открытия — ответом на STREAM_READ/WRITE. 15 STREAM_CANCEL: клиент отменяет конкретный поток. Поздние DATA не публикуются. 16 QUOTA: owner=UID, path/target пустые, cursor=0, data_len=0. Права чтения те же, что у STAT/LIST выбранного участника. Ответ data_len=40: used:u64, limit:u64, reserved:u64, files:u64, directories:u64, LE. Первые три значения в байтах, последние два — числа объектов. Снимок общий для UID во всех группах. Не резервирует место и не занимает квитанцию мутации. used может превышать limit после уменьшения лимита; сервер не выдаёт фиктивный нулевой размер при ошибке учёта. Открытие потока — обычный повторяемый запрос с отдельным request_id. cursor связывает его с DATA/END/CANCEL. Поток определяется group/peer/kind/ID; один ID не может одновременно обозначать разные открытия. Каждый пакет по-прежнему подписан и зашифрован. При закрытии/перезапуске router-канала его handles завершаются с IO_ERROR; байты разных эпох не смешиваются. Возобновление через новую эпоху пока не реализовано. Статусы (0..14): OK, ACCESS_DENIED, NOT_FOUND, INVALID_PATH, INVALID_RANGE, EXISTS, BUSY, STALE, NO_SPACE, IO_ERROR, BAD_PACKET, HASH_MISMATCH, CANCELLED, TIMEOUT, DIRECTORY_NOT_EMPTY. Мутации COMMIT/MKDIR/RENAME/REMOVE сохраняют успешный ответ до истечения 120 секунд от начала обработки (64 квитанции); тот же request_id с другим содержимым — BAD_PACKET. COMMIT дополнительно узнаётся по upload UUID. При заполнении квитанций новая мутация возвращает BUSY до истечения старых. В таком ответе cursor содержит остаток до ближайшего освобождения квитанции в timebase 0.1мс (1..1200000). Нулевой cursor означает BUSY другой причины с обычной задержкой 5 секунд. Клиент COMMIT/MKDIR/RENAME/REMOVE сохраняет pending и прежний request_id/параметры, ставит отменяемый таймер на этот срок и повторяет команду; промежуточный BUSY не завершает callback. При потере ответа обычный повтор идёт через 5 секунд. Общий срок от создания запроса остаётся 120 секунд и не продлевается даже при BUSY; pump не отправляет команду после этого срока. Поэтому повторы остаются внутри серверного окна квитанции, не требуя её досрочного вытеснения. Остальные команды возвращают BUSY caller. Повторы после этого срока не дают гарантии того же результата; перед новой мутацией требуется STAT. Потоки, таймауты и остановка --------------------------- На экземпляр один дисковый pthread с очередью до 64 обычных задач. Сеть, ACL, таймеры и state machines выполняются в uasync; open/stat/readdir/read/write, resize/hash/sync/rename/remove/close — в дисковом потоке. GUI только передаёт команды через bridge и получает события. Нет вызова дискового I/O из callback таймера. Completion резервируется до постановки задачи; без polling workers. Локальные файлы transfer и серверное дерево используют тот же дисковый поток. Для обязательной очистки резервируется отдельная задача до открытия файла. Обращение к своему серверу доставляется через отдельную ограниченную очередь call_soon в тот же обработчик, без сетевого канала. ACL и дисковый поток общие; сетевой recv не принимает unsigned даже при src == self. Сервер держит до 64 открытых файлов. Каждый READ/WRITE/BEGIN продлевает idle. После 10 секунд простоя fd закрывается на очередном дисковом sweep (таймер 1 с); занятый дисковый поток откладывает sweep до освобождения. Закрытие не публикует и не удаляет файл. Temp можно открыть заново до истечения upload TTL. После restart незавершённые temp удаляются; опубликованные файлы остаются. Лимиты: 16 клиентских запросов, 32 серверных задания/ответа, 16 временных загрузок, 16 клиентских и 16 серверных потоков на экземпляр. Каждый поток имеет кольцо 32 x 8192 байт (256 КиБ); worker обрабатывает до 16 блоков за задачу. Чтение следующей порции перекрывается с отправкой уже подготовленной. Все серверные и локальные I/O используют один FIFO worker, без блокировки uasync. Отправитель добавляет пакеты, пока send_q router находится под нижним порогом (сейчас count<=0), затем ждёт etcp_router_on_send_ready. Файловый слой не устанавливает своё окно inflight и не повторяет DATA; транспортные ACK, retransmit и порядок — router. Callback отправляет не более 8 блоков за запуск, уступая очередь другим потокам. Параллельные файлы одного пира разделяют канал и FIFO/round-robin waiters. Готовность приёмника проверяется router до доставки следующего seq. Заполненное кольцо или очередь заданий оставляют пакет в recv_q: rx_seq/ACK не продвигаются. Освобождение места вызывает отложенный resume; pump отменяется при reset/close. ACK RECEIVE_BUSY (flags bit0) раз в 100 мс отличает ожидание диска от потери транспорта. Отправитель приостанавливает новые DATA, а живые busy ACK продлевают таймаут router. Буфер router ограничен его receive window. Локальная доставка использует такую же проверку готовности и ограниченный FIFO, а не сеть или отдельные подтверждения. При остановке сначала отменяются producers/requests и дожидаются все stream cleanup continuations; только затем worker освобождает storage. Отмена после уже выполненной публикации не удаляет опубликованный файл. Transfer владеет CM handle до окончания/отмены; DOWN/TIMEOUT handle не освобождает. quiesce запрещает новые команды, отменяет таймеры, waiter и запросы; destroy вне callbacks дожидается только своих работ/completions и закрывает дисковый поток до уничтожения CHAT-групп. Контексты не освобождаются под работающим I/O. Отмена не прерывает текущий системный I/O или хеширование, а ждёт его completion. Немедленная отмена после start снимает starting: поздний CM UP не запускает STAT/подготовку заново. После перехода к финализации публикации повторный cancel не меняет уже выбранный результат. Desktop ------- Контекстное меню канала: Group files. Меню участника: Member files. Окно: выбор сервера/UID, путь, Up/Refresh, таблица каталогов/файлов, Upload/Download/New folder/Rename/Delete, прогресс и Cancel transfer. Чужой UID доступен только для чтения. Автор может быть offline. Open скачивает в выбранный пользователем локальный файл и открывает его приложением ОС после завершения. Смена контекста отбрасывает поздние ответы. Для выбранного UID строка хранилища показывает занято/всего, число файлов/каталогов и резерв текущих загрузок. Точные байты доступны в tooltip. QUOTA запрашивается отдельно от LIST при выборе сервера/UID, обновлении каталога, завершении передачи и каждые две секунды видимого окна. Поздние ответы старого UID/сервера игнорируются. BUSY при стартовом сканировании сохраняет Loading directory и повторяет LIST через секунду; новый переход отменяет повтор. API параллельных передач ----------------------- member_files_transfer_start(request, callback, arg) копирует request и возвращает отдельный mf_transfer* на каждый файл. upload задаёт направление, group/server/owner выбираются для каждой операции. id=0 выбирает случайный ID; id в callback — фактический. NULL означает отказ запуска без callback; причина пишется в лог. max_bytes=0 оставляет прежний лимит; ненулевое значение ограничивает размер файла. Upload проверяет размер источника до хеширования, download — ответ STAT до открытия локального temp. STREAM привязан к проверенным размеру/version; превышение — INVALID_RANGE. callback(arg, mf_transfer_progress*) получает id/done/total/status/finished. Прогресс заимствован до возврата callback. done для отправителя означает байты, принятые router в очередь; успешный finished подтверждает завершение операции, для upload — публикацию на сервере, для download — публикацию локального файла. finished приходит ровно один раз, handle к этому моменту уже освобождён; вызывающий обнуляет свой указатель в callback. member_files_transfer_cancel(handle) отменяет только этот файл, ждёт его I/O и ставит CANCEL в очередь с backpressure. Stop сервиса/группы завершает все принадлежащие ему handles. Все API/callbacks выполняются в uasync-потоке экземпляра. GUI trampolines используют тот же API и превращают callback в существующие события прогресса; Qt не делает I/O. Два download с одной строкой local одновременно не допускаются; физические aliases и подмену локальных путей исключает вызывающий. Upload читает стабильный regular source; изменение снимка возвращает STALE. На POSIX O_NONBLOCK позволяет отвергнуть FIFO до блокирующего open/read единственного дискового worker. Download создаёт соседний temp эксклюзивно и получает право cleanup только после успеха open. Отказ из-за существующего temp, max_bytes, STALE или ранняя отмена не удаляют чужой путь. После проверки SHA256 выполняются sync/close и заменяющий rename; до публикации прежний local сохраняется. POSIX-публикация локального download не делает fsync родительского каталога, в отличие от серверного COMMIT. Аватарки пользователей --------------------- Относительный путь avatars/.<расширение> находится внутри обычного дерева UID: <корень>///avatars/.<расширение>. Дополнительного UID в API/пути нет. Аватарка по умолчанию копируется в каждую группу, где выбранный сервер имеет подписанную storage-роль. Ссылка каждого канала публикуется только после COMMIT соответствующей копии; отсутствующий сервер в другом канале не создаёт битую ссылку. Новое членство обнаруживается через снимки участников и повторяет публикацию. Между серверами автоматического копирования нет. userinfo содержит строковые avatar_server (16 hex цифр) и avatar_file (64 hex SHA256 + .json). Оба поля отсутствуют при удалённой аватарке. Профиль подписан ключом участника; смена имени/адресов сохраняет остальные плоские JSON-атрибуты. userinfo ограничен 1024 байтами UTF-8. Merkle, JOIN_INFO_RESP, CHANNEL_INVITE и JOIN_REQUEST используют длину userinfo:u16 BE вместо прежнего u8. Bootstrap передаёт существующий подписанный профиль инвайтера, не создавая более свежую запись только с именем. Обе стороны нужно обновить: обратной совместимости нет. Снимок участника CHAT_MEMBER_DISPLAY_SIZE теперь 158 байт: прежние 79 байт, avatar_server:8LE, avatar_file:70 (с NUL), storage_role:1. storage_role обозначает подписанную роль сервера, независимо от существующей колонки storage — сетевого приоритета, который может быть 0 у сервера за NAT. Потребители используют общий chat_member.h; локальное изменение профиля также вызывает MEMBER_UPDATED. В Settings → Profile доступны Upload avatar, выбор storage-сервера, предпросмотр и Remove avatar. Изменения применяются сразу, независимо от Save других настроек. Перед загрузкой desktop/Android показывают редактор: неподвижная квадратная рамка, перемещение фото и масштабирование (мышь/колесо или жесты). Отмена не публикует новую ссылку. Worker принимает JPEG/PNG/WebP до 32 МиБ и 16 мегапикселей. Исходные байты, включая EXIF, сохраняются без изменений. Выбранная область после EXIF-ориентации становится WebP lossy 256×256, quality=85, максимум 1 МиБ. Описание JSON (максимум 4096 байт): version=1, original/preview — имена объектов; original_bytes/preview_bytes, width/height — размеры ориентированного оригинала; x/y/w/h — нормализованный квадрат в его координатах. Все объекты лежат в avatars/.. Порядок COMMIT: оригинал, превью, описание; затем подписанная ссылка на описание. Повтор использует уже сохранённые неизменяемые объекты. Сервер проверяет имя/SHA256, лимит объекта и запрещает замену под прежним именем и rename в/из avatars. Файлы и каталог входят в обычные квоты. Прежние версии сохраняются на сервере; владелец может удалить их через файловый браузер. Старые PNG-ссылки не поддерживаются. Клик по аватарке сообщения/участника открывает галерею пользователя: LIST каталога выбирает только описания, проходит все страницы cursor/version, сортирует mtime DESC, при равенстве — по имени. Изначально выбрана текущая подписанная ссылка. Большое фото — полный оригинал, до его загрузки показывается превью. Внизу пять квадратных позиций: две предыдущие, выбранная по центру, две следующие; за границами списка позиции пустые. Desktop: кнопки/клавиши ←/→; Android: кнопки/свайпы при исходном масштабе. Превью кликабельны. Загружаются выбранный оригинал и видимые превью; закрытие отменяет запросы без других владельцев. Для DM источник — общий канал, из которого берётся отображаемая аватарка. AvatarManager общий для списка участников и сообщений, максимум две операции одновременно, одинаковые хеши не скачиваются параллельно. Изменение своего профиля имеет приоритет перед фоновыми загрузками. Для DM используются ссылки из общих каналов; при отсутствии доступной ссылки остаются инициалы. Онлайн-статус списка отображается отдельной точкой поверх фотографии. Поздний результат старой ссылки пополняет кеш, но не заменяет картинку новой ссылки. Кеш аккаунта: /avatar_cache//.<расширение>; без db_path используется Qt AppLocalDataLocation. Проверяются SHA256 объектов, описание и WebP 256×256; превышение/битая картинка не публикуются. Публикация кеша атомарна через QSaveFile, незавершённые download/part очищаются при старте. Готовые файлы не требуют TTL/STAT: смена содержимого меняет имя. Дисковый LRU — 100 МиБ, кеш QImage — 32 МиБ; собственная аватарка и источники незавершённых upload сохраняются при вытеснении. Дисковое I/O и декодирование выполняются вне GUI/uasync. Повторы неудачных загрузок: 5/10/20/40/80/160 секунд; новая ссылка сбрасывает задержку. RTT/online не меняют ссылку и не вызывают повторной загрузки. Желаемая собственная ссылка/удаление сохраняются атомарно одной строкой ui_state.avatar_default, незавершённая публикация повторяется после запуска при наличии локального источника. test_avatars проверяет нормализацию, хеш/размеры, очистку temp, настоящее storage в двух каналах, публикацию, сохранение при смене имени, DM-кеш, restart без сети, автоматический download после потери кеша и удаление ссылок. Также проверяются max_bytes до открытия local temp и смена аватарки при задержанном download старой версии: поздний результат не заменяет новый рисунок. test_member_adapter проверяет подписанные профили 191/255/256/512/1023/1024 байт вместе с adm_tags до 8192. Категория media=debug показывает discovery, cache hit/eviction, направление, group/owner/server/hash, ошибки проверки и retry; media=info — передачи/публикации. Проверки и диагностика --------------------- tests/test_member_files: wire/UTF-8, произвольные блоки, повторы, EOF, версии, короткая замена, SHA256, пустой файл, независимые квоты UID, общий учёт по группам, резервирование/повторы/TTL, инкрементальные files/directories, замены пустых файлов, создание/удаление/rename каталогов, отсутствие runtime-rescan, стартовый scan и его ошибки, ссылки, expiry/restart, 10-секундный fd idle и принадлежность work/done потокам. На POSIX отдельный процесс с границей 2с проверяет неблокирующее открытие FIFO и последующий отказ проверки типа; fork выполняется до создания pthread. tests/test_member_files_network: настоящие A/B/M, подписанная storage-роль, параллельные upload/download одного пира, local loopback, пустые файлы, побайтная проверка, отмена одного handle, restart router и destroy с активным server I/O. Шестисекундная задержка worker проверяет receive backpressure и busy ACK. Также проверяются чужая запись, виртуальный корень и подписанное удаление читателя. QUOTA проверяется на настоящем подписанном/зашифрованном канале; удалённый читатель не получает счётчики. Проверены сохранность чужого temp/прежнего назначения при EXISTS, max_bytes и немедленном cancel, поздний CM UP после отмены, 76 созданий и 76 удалений с исчерпанием квитанций. Один успешный ответ отброшен перед клиентским handler: повтор с прежним ID возвращает квитанцию, счётчики остаются точными (76 каталогов после создания, 0 после удаления). test_services — stop/start общего чат-сервиса. Диагностика файлового сервиса — категория media, маршрутизатора — etcp_route. Настройка для полного трасса этих модулей: [debug] media=trace etcp_route=trace media=debug показывает запрос/ответ, ACL, очереди файлового сервиса, I/O и fd idle. etcp_route=debug добавляет выбор маршрута, состояния канала и ретрансмиты. Уровень trace дополнительно показывает каждый read/write и передачу задач/завершений между дисковым потоком и uasync. stream progress раз в секунду показывает ID/направление, done/total, число блоков в кольце, активность I/O, причину ожидания и скорость B/s. Router логирует переходы receive paused/resumed и peer receive busy без повторного сообщения на каждый блок. quota scan started/ready показывают путь, число пользователей и время обхода; quota updated показывает UID/операцию/used/reserved/files/directories; quota exceeded — used/reserved/growth/limit. По request_id видны GUI post → core request → router seq → server rx → ACL → disk queued/start/done → response tx → GUI result/bridge → directory ready. PID создания worker, PID отправки задачи и startup PID позволяют однозначно обнаружить fork после создания потока; несовпадение PID логируется как ошибка. Ключи шифрования, plaintext файлов и сырые медиа в debug не выводятся. Старые VAD/рация/GUI/вложения сообщения перенесены в vad/radio/chat/media/config; ошибки и пользовательские info остаются в тематических категориях.