Files
mayatnikovandClaude Opus 5 f9ac83bef8 Развести номера ADR: 064/065/066 этой ветки → 073/074/075
Ветка отведена от 522ba89, где последним был ADR-063, и заняла
064–072. Тем временем в main через #3 и #4 пришли свои 064
(строка ввода — div вместо form), 065 (стороны сообщений)
и 066 (серверного перца нет).

Переезжают три ADR этой ветки, 067–072 остаются на месте:
их номера свободны, а внутренние ссылки менять незачем.

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

140 lines
12 KiB
Markdown
Raw Permalink 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.
# План реализации v1
Документ для исполнителя — человека или агента. Всё, что здесь, выводится из ADR и спецификаций; при расхождении правы ADR. Этапы идут по порядку, каждый заканчивается работающим деплоем на `bare.xmatic.team` и коммитом.
## Источники истины
| вопрос | документ |
|---|---|
| что и почему | `philosophy.md`, `architecture.md`, `threat-model.md`, `decisions/` |
| криптография, байт в байт | `crypto.md` |
| HTTP-API, SSE, коды ошибок | `protocol.md` |
| схема SQLite, IndexedDB, формат `.bare` | `storage.md` |
| экраны, тексты, поведение | `ui.md`, `identity/` |
| сервер, nginx, systemd | `deploy.md` |
## Раскладка репозитория
```
embed.go //go:embed web в корне модуля (ADR-025)
cmd/bare/main.go подкоманды: serve, vapid, version
internal/build/ ревизия и время коммита из build info (ADR-074)
internal/config/ переменные BARE_*
internal/store/ SQLite, migrations/*.sql (embed), запросы
internal/auth/ argon2id, сессии, cookie
internal/hub/ SSE-соединения по deviceId
internal/push/ webpush-go, правила ADR-023
internal/api/ маршруты, валидация, лимиты, заголовки
internal/web/ embed web/, отдача статики
web/
index.html app.css manifest.json sw.js
icons/ уже в репозитории
js/main.js загрузка, роутинг, состояние
js/api.js fetch-обёртки, SSE, ACK
js/crypto.js всё из crypto.md
js/db.js IndexedDB из storage.md
js/sync.js устройство, поток событий, приём и отправка
js/pwa.js service worker, самообновление (ADR-068), пуши, установка
js/ulid.js ULID
js/zoom.js запрет масштабирования (ADR-075)
js/ui/*.js экраны из ui.md
js/export.js .bare
scripts/deploy.sh
```
Go — последняя стабильная версия, маршрутизация `net/http` с шаблонами методов (`"POST /api/messages"`). Прямые зависимости ровно три (ADR-020). Клиент — ES-модули, без сборки, без inline-стилей и скриптов, `innerHTML` запрещён.
## Этап 0 — скелет и деплой
- `go mod init`, `cmd/bare`, `serve` слушает `BARE_ADDR`, отдаёт `web/` из `embed`, `/healthz`, заголовки безопасности.
- `web/index.html` — страница со знаком и словом `bare`, `app.css` с переменными из `identity/brief.md`, `manifest.json`, пустой `sw.js` с версией.
- `scripts/deploy.sh`; на сервере — пользователь, каталоги, `env`, юнит, nginx, сертификат по `deploy.md`.
Готово, когда `https://bare.xmatic.team/` открывается с правильным CSP, `/healthz` отвечает `ok`, `journalctl -u bare` чист.
## Этап 1 — аккаунты
- Миграция 001, `store` с `user_version`, фоновая чистка.
- `auth`: argon2id с параметрами ADR-021, сессии, cookie, проверка `Origin`.
- Эндпоинты: `config`, `kdf`, `register`, `login`, `logout`, `me`, `password`, `DELETE /api/me`, `users/{nick}`.
- Клиент: `crypto.js` (мастер, authKey, kek, блоб, ключевая пара, отпечаток), экран входа и регистрации, сохранение `CryptoKey` в IndexedDB, автоповышение итераций, настройки с «сменить пароль» и «выйти».
- Тесты Go: миграции на пустой базе, регистрация и вход, неверный `authKey`, смена пароля с `logoutOthers`.
Готово, когда регистрация и вход работают на телефоне и десктопе, вход на втором устройстве расшифровывает тот же ключ (отпечатки совпадают), пароль в сетевых запросах не встречается.
## Этап 2 — чат 1:1
- `devices`, `hub`, `queue`, `POST /api/messages`, `ack`, `events` с воспроизведением очереди и пингом.
- Клиент: `ulid.js`, `db.js`, `api.js` с SSE и ACK после записи, шифрование сообщений, экран чата (десктоп и мобильный по эталону), список чатов, «новый чат», разделители дат и «новые», pending/failed, повтор после реконнекта.
- Контакты: `GET/POST/DELETE /api/contacts`, автосоздание при первом сообщении.
- Тесты Go: фан-аут по устройствам без эха отправителю, ACK удаляет, повтор очереди при реконнекте, `clock_skew`, лимит 30/мин.
Готово, когда два аккаунта переписываются в реальном времени, второе устройство получателя получает копию, офлайн-устройство получает очередь при открытии, в базе — только шифротекст.
## Этап 3 — ключи и комнаты
- TOFU: хранилище `peers`, карточка контакта, предупреждение о смене ключа, «доверять новому ключу», повторная расшифровка `raw`.
- Комнаты: `rooms` и `room_keys`, все эндпоинты из `protocol.md`, события `room`/`room_left`, передача владения, rekey при выходе.
- Клиент: создание комнаты, участники, заворачивание и разворачивание ключей, хранение `roomKeys`, отправка с текущим `keyId`, расшифровка любым известным.
- Тесты Go: `keys_mismatch`, `key_exists`, выход владельца, удаление пустой комнаты, обрезка ключей до двух.
Готово, когда трое переписываются в комнате, добавленный четвёртый читает только новое, вышедший не получает новых сообщений после rekey, подмена `public_key` в базе вручную вызывает предупреждение у собеседника.
## Этап 4 — PWA и пуши
- `sw.js`: кэш оболочки, `push`, `notificationclick`; `manifest.json` с иконками; `apple-touch-icon`.
- `PUT/DELETE /api/devices/{id}/push`, отправка по правилам ADR-023, обработка 404/410.
- Клиент: запрос разрешения после первого сообщения, настройки уведомлений, баннер установки на iOS, `beforeinstallprompt`.
Готово, когда закрытое PWA на iPhone и Android получает пуш и открывается на нужном чате; повторные сообщения до открытия пуш не порождают.
### Чеклист ручной проверки на устройствах
Автоматически проверено всё, что проверяется без настоящих устройств: правило «одно
молчащее устройство — один пуш», сброс `push_pending` при подключении SSE, удаление
подписки на 404/410, расшифровка пуша по RFC 8291 в тесте, отсутствие плейнтекста
в нагрузке, кэш оболочки без `/api/*`, отказ ходить на непубличные адреса. Осталось
то, что требует рук и телефона:
- [ ] **iPhone, установленное на «Домой» приложение**: пуш приходит при закрытом
приложении, нажатие открывает нужный чат.
- [ ] **Android Chrome, закрытое приложение**: то же самое.
- [ ] **Клик по системному уведомлению** в обоих случаях: в уже открытое окно
(фокус и переход) и при закрытом приложении (`/#/dm/<nick>`, `/#/room/<id>`).
- [ ] **Повторные сообщения до открытия**: второе и третье пуша не порождают.
- [ ] **iOS вне PWA**: баннер установки над списком чатов, текст, крестик и то,
что он больше не появляется; в настройках «уведомления» — текст про установку
вместо кнопки.
- [ ] **`beforeinstallprompt`** в обычном Chrome: раздел «установить приложение»
появляется, после нажатия исчезает целиком.
- [ ] **Системный запрос разрешения** после первого отправленного сообщения:
headless-Chrome отвечает `denied` сам, живой диалог не проверялся.
- [ ] **Прогон сценариев «готово, когда»** этапов 15 в Safari (iOS и десктоп)
и Firefox — автоматика гоняла только Chrome.
## Этап 5 — история
- Экспорт и импорт `.bare` по `crypto.md` и `storage.md`; идемпотентность; «архив создан другим аккаунтом».
- Настройки: устройства (список, удаление), занятое место, удаление аккаунта.
- Пагинация ленты по 50 с подгрузкой вверх.
Готово, когда экспорт с одного устройства и импорт на другом дают одинаковую ленту без дублей, повторный импорт ничего не меняет, чужой архив отклоняется до расшифровки.
## Этап 6 — закалка
- Rate limiting по всем правилам ADR-021, `413`, `429` с `Retry-After`.
- Проверка CSP в консоли браузера: ноль нарушений.
- Прогон модели угроз по коду: пароль не уходит, `from` ставит сервер, `Origin` проверяется, cookie с нужными флагами.
- Синхронизация документов с кодом: расхождение — правка документа через ADR или правка кода.
## Определение готовности v1
Все шесть этапов; тесты Go зелёные; ручной прогон сценариев из каждого «готово, когда» на iOS Safari (PWA), Android Chrome, десктопных Chrome, Firefox, Safari; `README.md` обновлён со статуса «проектирование».
## Правила для исполнителя
- Сомнение в спецификации — сначала ADR, потом код. Не дописывать спецификацию молча.
- Новая зависимость, новый эндпоинт, новое поле в конверте — только через ADR.
- Каждый этап — отдельный коммит или серия коммитов с деплоем; не копить.
- Не добавлять фич сверх `ui.md`: ни тем, ни аватаров, ни «печатает», ни статусов прочтения.