diff --git a/AGENTS.md b/AGENTS.md index 0fbb21c..7b15960 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -235,7 +235,7 @@ bind2_archive_latency_network.py, archive_edges.rs, session_routing_isolation.rs После изменений обновляйте точные границы совместимости и результаты проверки. Не перезаписывайте исторические отчёты как будто они относятся к новой сборке. В передаваемый ZIP включайте AGENTS.md, workflow, vendor reference, исходники, -документацию и scripts; исключайте target, demo data, bin, .git, __pycache__, pyc +документацию и scripts; исключайте target, demo data, bin, .git, **pycache**, pyc и секреты. Сохраняйте executable bits shell scripts и проверяйте содержимое ZIP. ## Durable message IO 0.8.3 @@ -298,3 +298,34 @@ Final binary SHA-256: 8ceb8de3a2d02bc4399b8ae817dff7e3c3fe9bff83dc0c6669b7f19210 Подробные samples/границы — docs/VALIDATION-0.8.3.md. Checksum и JSON syntax всех frames проверяются при SM replay, но Record создаётся только для последнего snapshot. Recovery WAL sync до публикации обязателен; EIO fixture защищает это. + +## Monal MAM baseline 0.8.4 + +Баг: «archive redo baseline changed in place» на живом сервере — внешняя +перезапись native `.list` поверх зафиксированного redo-baseline (бакап/копия +data_path/Prosody/старая версия) делала каждый `archive_for_user` Err, `?` ронял +session, клиент (Monal-iOS) реконнектился и не доставлял сообщение. Причина не в +обычном append — baseline ломает только перезапись первых `base` байт в обход +`.rredo`; наш Rust-append пишет только в конец. + +Фикс (commit 1000053): 1) MAM-архивация на личной доставке (отправитель, +получатель, входящая федерация) стала best-effort `archive_message_best_effort` — +storage-ошибка не роняет session и не роняет доставку, в лог `mam-archive-skipped` +(санитизированный `sanitized_label`, без payload); 2) offline `storage-checkpoint` +теперь ремонтирует непроверенный baseline: `prepare_rewrite` через +`is_untrusted_redo` считает текущий синхронизированный native авторитетным и +удаляет неприменимый журнал (`retire_redo`). Живой append остаётся fail-closed. +Новые tests: `archive.rs` `runtime_append_fails_closed_...`/ +`offline_checkpoint_retires_...`, сетевой `tests/mam_broken_baseline_network.py` +(перезапись baseline не роняет доставку; checkpoint возвращает MAM). + +Сравнение с Prosody: `util/datamanager.lua` `list_append` пишет в конец, а +`list_store` (перезапись) делает atomic rename — меняет первые bytes baseline; +`plugins/mod_storage_internal.lua` `archive:append` для MUC/`key` и trim/delete +делает `list_store` (полная перезапись). Prosody не понимает `.rredo` sidecar. Наш +native `.list` сохраняет Prosody-совместимый `item(...)` формат. + +Операционный ремонт пользователя: остановить сервер, `./target/release/rust-xmpp +storage-checkpoint CONFIG`, запустить снова. `--experimental` не снимать. Полная +замена Prosody требует реального CI, внешней федерации, настоящих клиентов/push, +deployment/UID/TLS, rollback и длительной нагрузки — см. COMPATIBILITY.md. diff --git a/docs/COMPATIBILITY.md b/docs/COMPATIBILITY.md index 01b96dd..1618062 100644 --- a/docs/COMPATIBILITY.md +++ b/docs/COMPATIBILITY.md @@ -7,23 +7,23 @@ неизвестные явно включённые MUC-модули отклоняются при запуске. Это не означает полного совпадения поведения с Prosody. -| Область | Что реализовано | Существенные пределы | -|---|---|---| -| Конфигурация | Lua/JSON/YAML/YML/TOML, Include, глобальные и локальные области, наследование модулей | Lua VM ограничена; не все функции окружения конфигуратора Prosody доступны. Runtime обслуживает один VirtualHost и его компоненты | -| Хранилище | internal `.dat`, `.list`, бинарные строки, смешанные ключи Lua, атомарные записи, потоковые архивы, инвалидирование `.lidx` | SQL, external storage остальных модулей и произвольные драйверы не реализованы; S3 uploads поддержаны. Каталог блокируется между процессами Rust; Prosody в блокировке не участвует | -| Аккаунты, saslauth | internal_hashed SHA-1/SHA-256, SASLprep, PLAIN внутри TLS, SCRAM и PLUS с tls-exporter/tls-server-end-point, SSDP, SASL2 | Только internal_hashed; опциональный sasl2_bind2 (alias bind2) поддерживает inline bind/SM/CSI/Carbons. SASL2 inline SM resume и takeover живого транспорта реализованы; Bind2/MAM metadata читает endpoints без полного scan; полные MAM queries всё ещё могут сканировать историю | -| Сообщения и presence | локальная маршрутизация, приоритеты ресурсов, контакты/подписки/probes, offline, carbons, блокировки | Полная совместимость всех пограничных сценариев маршрутизации ещё не подтверждена | -| MAM | личный и комнатный MAM2, prefs, формы, RSM, фильтры времени/контакта/ID, extended queries, metadata, stanza-id, expiry | Нет доказательства совпадения каждой ошибки и порядка результатов при всех комбинациях запроса | -| MUC | комнаты, несколько ресурсов, роли/affiliations, owner/admin, конфигурация, приглашения, история без MAM и MAM, occupant-id/avatar, hats, PM/presence policies, регистрация, административные ad-hoc команды; журнал конфигурации, членства и истории до ответа, crash recovery persistent/temporary комнат | Rust-журнал авторитетен, native config/state — экспорт. Без SM восстановление требует новой аутентификации точного полного JID; grace по умолчанию 300 с, `muc_recovery_grace_period` 1..604800. Действующая приватная SM-запись восстанавливает логическое членство до listeners. Произвольные Lua-модули не запускаются; [MUC-RECOVERY.md](MUC-RECOVERY.md) | -| PEP/pubsub | persistent nodes/items, ACL, subscriptions, publish/retract/purge/delete, owner/configuration, caps, OMEMO XML как payload | Не все необязательные политики узлов Prosody реализованы; OMEMO шифруют клиенты, криптография OMEMO не часть сервера | -| private/bookmarks/vCard | private XML, старые и новые закладки, vCard/temp-vCard4 и avatar-переходы | Межклиентное поведение нужно дополнительно проверить настоящими клиентами | -| SM/push | acknowledgements после обработки, resumable XEP-0198 sessions, replay, ограниченные очереди, приватный durable snapshot, heartbeat и shutdown checkpoint; legacy SASL/SASL2 resume после restart; native регистрации push | Нужны новый TLS/SASL-транспорт и токен того же владельца; TCP/TLS-сокеты не сохраняются. SM-снимок не native Prosody: 256 MiB суммарно, 8 MiB/4096 stanza на сессию. При неопределённой отправке возможен replay. Есть coalescing и watchdog первого push; реальный APNs/FCM цикл не подтверждён | -| CSI | active/inactive, ограниченный буфер, native importance function + runtime defaults для OMEMO/receipts/markers, delay, flush при клиентской активности и grace period | Defaults0.8.2 важнее по доставке, чем строгая native policy; явный csi_important_payloads заменяет defaults. Событие socket drain приближено завершением записи; необязательные расширенные XPath-фильтры поддержаны не полностью | -| Федерация | входящие/исходящие STARTTLS, CA/DNS-ID, SASL EXTERNAL, DNS SRV, проверка домена каждого stanza, ограниченный пул соединений | Нет dialback/DANE/OtherName-only/SRV-ID/bidi/direct TLS | -| HTTP upload | Несколько XEP-0363 components, Host/path isolation, local/S3 gateway, multipart/SigV4, streaming PUT, GET/HEAD/range/conditional, квоты/expiry, native metadata/JWT; offline local↔S3/S3↔S3 migration с SHA-256/resume; intent/exact-key recovery и quarantine; реальный 10 GiB PUT/GET через MinIO проверен | MinIO single-node с описанным netlink adapter тестовой среды; AWS/distributed deployment не проверены. Миграция требует остановки сервера и временного места под один файл; source сохраняется. Потерянные ownership metadata и неопределённый Complete требуют отдельного разбора; HTTP-UPLOAD.md | -| WebSocket/BOSH | WS/WSS и HTTP/HTTPS, общая XMPP-сессия, origin rules, BOSH RID/retry/hold/reorder/restart; http_altconnect XRD/JRD discovery; http_paths, HTTP Host/fallback, внешний URL, доверенные IP/CIDR и forwarded headers для TLS reverse proxy | Один VirtualHost с компонентами; нет PROXYv1/v2, произвольных Lua HTTP-приложений и горячей перенастройки. См. HTTP-PROXY.md | -| admin_shell | протокол admin socket, status/version/uptime, списки, основные операции аккаунтов | Нет произвольного Lua, горячей загрузки модулей и полного набора prosodyctl | -| Остальные модули | disco, ping, time, version, uptime, register, TURN extdisco/HMAC, каталог seclabels | Нет встроенного TURN. Coturn работает отдельно. `pep_simple` пуст в предоставленном исходнике | +| Область | Что реализовано | Существенные пределы | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Конфигурация | Lua/JSON/YAML/YML/TOML, Include, глобальные и локальные области, наследование модулей | Lua VM ограничена; не все функции окружения конфигуратора Prosody доступны. Runtime обслуживает один VirtualHost и его компоненты | +| Хранилище | internal `.dat`, `.list`, бинарные строки, смешанные ключи Lua, атомарные записи, потоковые архивы, инвалидирование `.lidx` | SQL, external storage остальных модулей и произвольные драйверы не реализованы; S3 uploads поддержаны. Каталог блокируется между процессами Rust; Prosody в блокировке не участвует | +| Аккаунты, saslauth | internal_hashed SHA-1/SHA-256, SASLprep, PLAIN внутри TLS, SCRAM и PLUS с tls-exporter/tls-server-end-point, SSDP, SASL2 | Только internal_hashed; опциональный sasl2_bind2 (alias bind2) поддерживает inline bind/SM/CSI/Carbons. SASL2 inline SM resume и takeover живого транспорта реализованы; Bind2/MAM metadata читает endpoints без полного scan; полные MAM queries всё ещё могут сканировать историю | +| Сообщения и presence | локальная маршрутизация, приоритеты ресурсов, контакты/подписки/probes, offline, carbons, блокировки | Полная совместимость всех пограничных сценариев маршрутизации ещё не подтверждена | +| MAM | личный и комнатный MAM2, prefs, формы, RSM, фильтры времени/контакта/ID, extended queries, metadata, stanza-id, expiry | Нет доказательства совпадения каждой ошибки и порядка результатов при всех комбинациях запроса | +| MUC | комнаты, несколько ресурсов, роли/affiliations, owner/admin, конфигурация, приглашения, история без MAM и MAM, occupant-id/avatar, hats, PM/presence policies, регистрация, административные ad-hoc команды; журнал конфигурации, членства и истории до ответа, crash recovery persistent/temporary комнат | Rust-журнал авторитетен, native config/state — экспорт. Без SM восстановление требует новой аутентификации точного полного JID; grace по умолчанию 300 с, `muc_recovery_grace_period` 1..604800. Действующая приватная SM-запись восстанавливает логическое членство до listeners. Произвольные Lua-модули не запускаются; [MUC-RECOVERY.md](MUC-RECOVERY.md) | +| PEP/pubsub | persistent nodes/items, ACL, subscriptions, publish/retract/purge/delete, owner/configuration, caps, OMEMO XML как payload | Не все необязательные политики узлов Prosody реализованы; OMEMO шифруют клиенты, криптография OMEMO не часть сервера | +| private/bookmarks/vCard | private XML, старые и новые закладки, vCard/temp-vCard4 и avatar-переходы | Межклиентное поведение нужно дополнительно проверить настоящими клиентами | +| SM/push | acknowledgements после обработки, resumable XEP-0198 sessions, replay, ограниченные очереди, приватный durable snapshot, heartbeat и shutdown checkpoint; legacy SASL/SASL2 resume после restart; native регистрации push | Нужны новый TLS/SASL-транспорт и токен того же владельца; TCP/TLS-сокеты не сохраняются. SM-снимок не native Prosody: 256 MiB суммарно, 8 MiB/4096 stanza на сессию. При неопределённой отправке возможен replay. Есть coalescing и watchdog первого push; реальный APNs/FCM цикл не подтверждён | +| CSI | active/inactive, ограниченный буфер, native importance function + runtime defaults для OMEMO/receipts/markers, delay, flush при клиентской активности и grace period | Defaults0.8.2 важнее по доставке, чем строгая native policy; явный csi_important_payloads заменяет defaults. Событие socket drain приближено завершением записи; необязательные расширенные XPath-фильтры поддержаны не полностью | +| Федерация | входящие/исходящие STARTTLS, CA/DNS-ID, SASL EXTERNAL, DNS SRV, проверка домена каждого stanza, ограниченный пул соединений | Нет dialback/DANE/OtherName-only/SRV-ID/bidi/direct TLS | +| HTTP upload | Несколько XEP-0363 components, Host/path isolation, local/S3 gateway, multipart/SigV4, streaming PUT, GET/HEAD/range/conditional, квоты/expiry, native metadata/JWT; offline local↔S3/S3↔S3 migration с SHA-256/resume; intent/exact-key recovery и quarantine; реальный 10 GiB PUT/GET через MinIO проверен | MinIO single-node с описанным netlink adapter тестовой среды; AWS/distributed deployment не проверены. Миграция требует остановки сервера и временного места под один файл; source сохраняется. Потерянные ownership metadata и неопределённый Complete требуют отдельного разбора; HTTP-UPLOAD.md | +| WebSocket/BOSH | WS/WSS и HTTP/HTTPS, общая XMPP-сессия, origin rules, BOSH RID/retry/hold/reorder/restart; http_altconnect XRD/JRD discovery; http_paths, HTTP Host/fallback, внешний URL, доверенные IP/CIDR и forwarded headers для TLS reverse proxy | Один VirtualHost с компонентами; нет PROXYv1/v2, произвольных Lua HTTP-приложений и горячей перенастройки. См. HTTP-PROXY.md | +| admin_shell | протокол admin socket, status/version/uptime, списки, основные операции аккаунтов | Нет произвольного Lua, горячей загрузки модулей и полного набора prosodyctl | +| Остальные модули | disco, ping, time, version, uptime, register, TURN extdisco/HMAC, каталог seclabels | Нет встроенного TURN. Coturn работает отдельно. `pep_simple` пуст в предоставленном исходнике | ## Особенности именно вашего конфига @@ -172,3 +172,14 @@ ad-hoc destroy/set-affiliation. Общие и host `modules_disabled` приме MAM/SM используют authoritative redo sidecars. Перед сменой writer или откатом на старую версию требуется offline `storage-checkpoint CONFIG`; после аварии нельзя просто удалить sidecars. Подробности — [DURABLE-MESSAGE-IO.md](DURABLE-MESSAGE-IO.md). + +### Внешний перезапись MAM baseline + +Внешняя перезапись native `.list` поверх зафиксированного redo-baseline (бакап, +копия data_path, Prosody, старая версия) давала «archive redo baseline changed +in place»: каждый MAM-append падал и через `?` ронял session, клиент +реконнектился, сообщение не доставлялось. В 0.8.4 MAM-архивация на личной +доставке стала best-effort (storage-ошибка не роняет session и не роняет доставку; +в лог — `mam-archive-skipped`), а `storage-checkpoint` получил ремонт непроверенного +baseline: текущий синхронизированный native считается авторитетным и неприменимый +журнал удаляется. Живой append остаётся fail-closed: журнал не удаляется молча. diff --git a/docs/DURABLE-MESSAGE-IO.md b/docs/DURABLE-MESSAGE-IO.md index 8dca692..8c9bdff 100644 --- a/docs/DURABLE-MESSAGE-IO.md +++ b/docs/DURABLE-MESSAGE-IO.md @@ -66,20 +66,50 @@ in-place append при активном redo считается незаверш Редактирование baseline in place без handoff не поддержано. Anchor проверяет последние 4 KiB baseline, не является checksum всего старого архива. +## Внешний перезапись baseline: «archive redo baseline changed in place» + +Если какой-либо writer (бакап-восстановление, копия data_path, Prosody, старая +версия сервера) перезаписал native `.list` поверх зафиксированного baseline без +handoff, `.rredo` перестаёт совпадать со своим anchor. Каждый вызов +`archive_for_user` возвращал `Err`, и через `?` ошибка роняла session +(`connection-closed reason=protocol-or-transport-error`) — клиент (напр. Monal) +реконнектился по кругу, сообщение не доставлялось. + +Начиная с 0.8.4 MAM-архивация на трёх местах личной доставки (отправитель, +получатель, входящая федерация) стала best-effort: при storage-ошибке исходная +stanza всё равно маршрутизируется, обе сессии остаются живы, в лог пишется +санитизированный `mam-archive-skipped`. Живой append остаётся fail-closed: +непроверенный журнал не удаляется молча, а сохраняется для явного handoff. + +Ремонт выполняется той же командой offline handoff: + +```bash +rust-xmpp storage-checkpoint /etc/prosody/prosody.cfg.lua +``` + +Теперь `prepare_rewrite` распознаёт непроверенный baseline (changed in place, +shorter, missing, journal exceeds bound), считает текущий durably-синхронизированный +native авторитетным, синхронизирует его и удаляет неприменимый журнал. После этого +MAM history снова читается, а новые append создадут свежий redo поверх нового +baseline. Сверку совместимости native-формата см. в исходниках Prosody +(`util/datamanager.lua` `list_append`/`list_store`, `plugins/mod_storage_internal.lua` +`archive:append`): Prosody пишет `item(...)` и при полной перезаписи применяет +atomic rename — что и меняет первые bytes baseline, оставляя Rust-журнал устаревшим. + ## Диагностика `message-processing-slow` по-прежнему имеет threshold 250 мс, duration_ms, route и connection. Новые поля в integer milliseconds: -|Поле|Измеряемый этап| -|---|---| -|pre_routing_ms|Работа до личного MAM; для MUC/ранних выходов — обработка до handled| -|sender_mam_ms|Политика и архивирование отправителя| -|recipient_mam_ms|Проверка аккаунта, политика и архивирование получателя| -|delivery_ms|Маршрутизация, targets, mutex и transmit| -|carbons_ms|Выбор ресурсов и отправка carbon| -|offline_push_ms|Offline commit и подготовка/enqueue push| -|handled_ms|Session mutex и durable mark_handled| +| Поле | Измеряемый этап | +| ---------------- | -------------------------------------------------------------------- | +| pre_routing_ms | Работа до личного MAM; для MUC/ранних выходов — обработка до handled | +| sender_mam_ms | Политика и архивирование отправителя | +| recipient_mam_ms | Проверка аккаунта, политика и архивирование получателя | +| delivery_ms | Маршрутизация, targets, mutex и transmit | +| carbons_ms | Выбор ресурсов и отправка carbon | +| offline_push_ms | Offline commit и подготовка/enqueue push | +| handled_ms | Session mutex и durable mark_handled | Неисполненные этапы равны 0. target_count — выбранные локальные ресурсы, carbon_count — успешные carbon transmit. Это серверные этапы, не время