Files
bare/docs/decisions/060-unknown-device-keeps-pending.md
T
mayatnikovandClaude Opus 5 db45978b16 Этап 6: закалка — лимиты ADR-021, аудит модели угроз, сверка документов
Лимиты: все четыре правила ADR-021 — регистрация 5/час на IP, вход 10/10 мин
на IP и ник, сообщения 30/мин, прочие изменяющие 60/мин; 429 с Retry-After;
X-Real-IP читается только с loopback, иначе адрес соединения — иначе заголовок
отменял бы лимит на IP; карты вёдер ограничены поколениями.

Аудит нашёл то, что пропустили пять раундов ревью:
ADR-056: nginx вёл access_log с IP и полными путями вопреки обещанию deploy.md.
Ники и социальный граф ложились в /var/log/nginx рядом с чистым журналом bare.
ADR-058: «выйти на других устройствах» не обрывал уже открытый SSE — отозванная
сессия продолжала получать сообщения.
ADR-059: промежуточный ключ комнаты был невосстановим. Участник, пропустивший
офлайн два rekey подряд, навсегда не расшифровал бы сообщения среднего ключа —
вопреки обещанию storage.md о повторной попытке после получения keyId.
ADR-063: ACK уходил по одному на конверт, а не пачкой. Получатель в оживлённой
комнате выедал общее ведро подтверждениями и упирался в 429 на всех изменяющих
запросах, включая выход из комнаты: 116 отказов за прогон стало нулём.
ADR-055, 057, 060, 061, 062: ключи вёдер и границы, +dirty у bare version,
403 unknown_device не хоронит сообщение, усечение имени в подсказке ввода,
kdf как оракул после повышения цели KDF.

Модель угроз пополнена тем, что действительно видит оператор: push-подписки
лежат в базе открытым текстом, и вместе с VAPID-ключом с той же машины это
произвольное уведомление на экране блокировки.

README приведён к v1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
2026-08-23 06:22:52 +03:00

24 lines
3.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-060: `403 unknown_device` не хоронит сообщение
Уточняет [ADR-033](033-failed-message-reason.md) и правило `docs/storage.md` про судьбу исходящего.
## Контекст
`docs/storage.md` делит отказы на два класса: сетевая ошибка и `500` оставляют сообщение `pending` и повторяются при следующем подключении, прочие `4xx``failed` с текстом отказа.
`403 unknown_device` в этот раздел не укладывается. Он означает не «сообщение не годится», а «устройства, от имени которого мы пишем, у сервера больше нет»: его удалили с другого устройства, либо оно отмерло по сроку (ADR-017). Текст у сообщения при этом появился бы посторонний — про сервер, который не справился, — а «повторить» не сработало бы ни разу: тот же `X-Device` получит тот же отказ.
Чинится это не сообщением, а устройством: клиент переподключается, `POST /api/devices` заводит устройство заново, и неотправленное уходит после `ready`. Клиент так и делал — оставлял запись `pending` и заводил повтор, — но в документах исключения не было, а `CLAUDE.md` запрещает дописывать спецификацию молча.
## Решение
- `403 unknown_device` — не отказ сообщению, а потерянное устройство: запись остаётся `pending`, клиент переподключается и повторяет её после `ready`.
- Правило записано строкой в `docs/storage.md` рядом с прежним делением отказов.
- Остальные `4xx` не меняются: `failed` с текстом отказа.
## Следствия
- Удаление устройства с другого устройства не превращает набранное в отвергнутое: сообщение уходит, как только устройство завелось заново.
- Перечень причин, по которым сообщение остаётся `pending`, становится закрытым: сеть, `500`, отложенная отправка (нет ключа комнаты, ключ собеседника ждёт подтверждения) и потерянное устройство.
- Бесконечного круга нет: пока устройства нет, сообщение просто лежит; полоса про отказ отправки в чате не появляется, потому что отказа сообщению не было.