docs: MAM baseline 0.8.4 — best-effort archival and offline repair

Document the 'archive redo baseline changed in place' failure, the runtime
session isolation, the offline storage-checkpoint repair, and the Prosody
native-format comparison (list_append in-place vs list_store atomic rename).
This commit is contained in:
wt committed 2026-10-21 18:57:51 +07:00
1 parent 100005379b
commit 75bb730d3c
3 files changed
+99 -27

No files matched your search

+32 -1
View File
@@ -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.
+28 -17
View File
@@ -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: журнал не удаляется молча.
+39 -9
View File
@@ -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. Это серверные этапы, не время