diff --git a/CHANGELOG.md b/CHANGELOG.md
index b2e7936..278b4b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -44,6 +44,14 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
live document that owns the subject — `docs/architecture.md` (with section),
`docs/product.md`, `docs/security.md`, or the README. Comments only; no
behaviour is affected.
+- `docs/code-review.md` is gone. Its plan is finished — phases 0 (bar the
+ release-commit steps), 1, 1.5, 2 and 3 are all closed — and the rest of the
+ document had become a second copy of what `architecture.md`, `security.md`
+ and the code comments already say. What was genuinely open moved to
+ `docs/roadmap.md`: splitting `internal/web` into subpackages, a consolidated
+ documentation index in the README, the adaptive polling interval for an idle
+ but visible tab, and `CONTRIBUTING.md`. The review text stays in git history
+ (`522425a`); the CHANGELOG entries below that cite it are left as written.
- `docs/architecture.md` gained a *Code layers* section: a diagram of
handlers → services → store plus the adapters, and the reason the services
layer exists (multi-store writes and their rollback) — closing item A2 of
diff --git a/docs/code-review.md b/docs/code-review.md
deleted file mode 100644
index 88d5d9e..0000000
--- a/docs/code-review.md
+++ /dev/null
@@ -1,472 +0,0 @@
-# Рецензирование кодовой базы SelfPost
-
-**Дата:** 2026-08-05
-**Объём ревью:** ~172 файла, 64 Go-исходника (~10 770 строк в `internal/` + `cmd/`), 29 unit-тестов, e2e-модуль `test/e2e/`, 17 HTML-шаблонов, 15 doc-файлов.
-
-Связанные документы: [architecture.md](architecture.md), [security.md](security.md), [implementation-plan.md](implementation-plan.md), [progress.md](progress.md), [roadmap.md](roadmap.md).
-
----
-
-## Общая оценка
-
-| Критерий | Оценка | Комментарий |
-|----------|--------|-------------|
-| Архитектура | **Отлично** | Чёткое слоение, минимум связности |
-| Сложность vs масштаб | **Отлично** | Без over-engineering |
-| Качество кода | **Отлично** | Идиоматичный Go, продуманные rollback-пути |
-| Документация | **Хорошо** | As-built docs точны; есть RU/EN split и stale comments |
-| Поддерживаемость | **Хорошо** | Высокий порог входа из-за phase-комментариев |
-| Логические ошибки | **Минимально** | Критичных багов не найдено; есть принятые операционные gap'ы |
-| Legacy | **Низкий** | Только архив spec + phase-комментарии |
-| GUI | **Хорошо** | Нет «костылей»; осознанные CSP/layout компромиссы |
-
-**Вывод:** проект готов к релизному тегу после закрытия § D ([implementation-plan.md](implementation-plan.md)) — предрелизного security review. Остальное — polish, не блокеры.
-
-**Статус на 2026-08-06:** § D закрыт, фазы 1, 1.5, 2 и 3 выполнены, добор по §§ 1/3/4 сделан. Незакрытым остаётся только то, что делается в момент резки версии: бамп тега образа в compose и сам git-тег ([roadmap.md](roadmap.md) § v1.x).
-
----
-
-## 1. Архитектура и структура проекта
-
-### Текущая структура
-
-```mermaid
-flowchart TB
- subgraph cmd [cmd]
- panel["panel (HTTP + milter + logtail)"]
- backup["selfpost-backup CLI"]
- end
- subgraph web [internal/web — 3030 LOC]
- handlers["handlers_*.go"]
- templates["templates/*.html"]
- security["security.go, session.go"]
- end
- subgraph services [Services]
- domainSvc["internal/domain"]
- appSvc["internal/app"]
- end
- subgraph persistence [Persistence]
- store["internal/store (SQLite)"]
- end
- subgraph adapters [Adapters]
- postfix["internal/postfix"]
- milterPkg["internal/milter"]
- logtail["internal/logtail"]
- dnscheck["internal/dnscheck"]
- backupPkg["internal/backup"]
- health["internal/health"]
- end
- panel --> web
- panel --> milterPkg
- panel --> logtail
- web --> domainSvc
- web --> appSvc
- domainSvc --> store
- appSvc --> store
- milterPkg --> store
- logtail --> store
- domainSvc --> postfix
- appSvc --> postfix
-```
-
-### Сильные стороны
-
-- **Layered / ports-and-adapters:** handlers → services (`domain`, `app`) → `store`; инфраструктура изолирована в адаптерах ([`internal/web/web.go`](../internal/web/web.go), [`internal/app/service.go`](../internal/app/service.go)).
-- **Composition root** в [`cmd/panel/main.go`](../cmd/panel/main.go): три роли (HTTP, journal-milter, log-tailer) в одном процессе — оправдано для single-container deployment.
-- **Interface seams** для тестов: `milter.Store`, `app.SenderMaps`, `logtail.StatusStore`.
-- **Embedded migrations** ([`internal/store/store.go`](../internal/store/store.go)) — простой, надёжный подход для 3 миграций.
-- **E2E как отдельный модуль** (`test/e2e/go.mod`) — не загрязняет основной модуль.
-
-### Замечания (не блокеры)
-
-- **`internal/web/` — 47 файлов, 3030 строк** — самый крупный пакет. При росте v2.x (роли, inbound relay) стоит выделить подпакеты (`web/handlers`, `web/auth`) или split по доменам. Сейчас — приемлемо.
-- **Один SQLite connection** (`MaxOpenConns(1)`) — сознательный trade-off; при росте нагрузки milter + HTTP + logtail будут сериализованы. Документировано, для single-admin panel — норма.
-
-### Рекомендации
-
-| # | Действие | Приоритет |
-|---|----------|-----------|
-| A1 | Оставить текущую структуру; рефакторинг пакетов — только при старте 2.x | Низкий |
-| A2 | Добавить в [architecture.md](architecture.md) диаграмму слоёв (как выше) — **выполнено** (§ Code layers) | Низкий |
-
-**Модель:** Sonnet (документация)
-
----
-
-## 2. Соответствие сложности масштабу проекта
-
-### Факты
-
-- Outbound SMTP relay + admin panel для одного оператора.
-- ~10.7K строк Go, 3 зависимости (`go-milter`, `x/crypto`, `modernc.org/sqlite`).
-- Нет ORM, нет SPA, нет message queue — всё уместно.
-
-### Сильные стороны
-
-- Нет лишних абстракций (нет generic repository, нет DI-фреймворка).
-- Сервисный слой тонкий, но достаточный: координация SQLite + sasldb2 + Postfix maps ([`internal/app/service.go`](../internal/app/service.go)).
-- Fail-open/fail-closed решения явно задокументированы (OpenDKIM tempfail vs journal fail-open).
-
-### Замечания
-
-- **Rate limiting в двух местах** (Postfix anvil L1 + milter L2) — сложность оправдана спецификацией, но требует понимания оператором.
-- **Backup с manifest version gate** ([`internal/backup/backup.go`](../internal/backup/backup.go)) — чуть тяжелее минимума, но оправдано для migration safety.
-
-**Вердикт:** сложность **адекватна** масштабу. Over-engineering не обнаружен.
-
----
-
-## 3. Качество написанного кода
-
-### Сильные стороны
-
-- **Комментарии объясняют «почему»**, не «что» — образцовый уровень ([`internal/web/security.go`](../internal/web/security.go), [`internal/milter/milter.go`](../internal/milter/milter.go)).
-- **Rollback-паттерны** при partial failure ([`internal/app/service.go`](../internal/app/service.go) `rollbackCreate`).
-- **Ordering guarantees:** SQLite row before SASL write — защита от race и password clobber.
-- **Validation centralized:** [`internal/web/validate.go`](../internal/web/validate.go), [`internal/app/validate.go`](../internal/app/validate.go).
-- **Atomic file writes** для конфигов ([`internal/postfix/write.go`](../internal/postfix/write.go), [`internal/domain/dkim.go`](../internal/domain/dkim.go)).
-- **Test coverage ~45%** file ratio (29 test / 64 source files); ключевые пути покрыты (auth, sessions, milter, backup, dnscheck).
-
-### Замечания
-
-| Файл | Замечание | Severity |
-|------|-----------|----------|
-| [`internal/web/token.go`](../internal/web/token.go) | `panic` при сбое `crypto/rand` — осознанно, документировано | Info |
-| [`internal/web/handlers_domains.go`](../internal/web/handlers_domains.go) | Stale comment: «Applications and send log arrive in later phases» — **закрыто** (Фаза 1) | Low |
-| Phase/spec references | ~50+ файлов с «Phase N», «spec 7.x» — **закрыто**: «Phase N» в Фазе 1, ссылки на архивную спецификацию — отдельным проходом (см. § 4) | Low |
-| [`cmd/panel/main.go`](../cmd/panel/main.go) | Package comment всё ещё упоминает «Phase 1 stubs» — **закрыто** (Фаза 1) | Low |
-
-### Потенциальные улучшения качества
-
-- Единый проход **gofmt + удаление stale phase-комментариев** (механическая работа) — **выполнено** (Фаза 1; `gofmt -l` теперь и в CI).
-- Добавить **table-driven test** для edge cases в `parseDelivery` (exotic Postfix status values) — **выполнено**, и проход оказался не косметическим: он вскрыл реальный баг. Шаблон разбора брал `status=` жадно, то есть **последнее** вхождение в строке, а Postfix дописывает в конец ответ удалённого сервера дословно. Отказ, в тексте ответа которого встречалось `status=sent`, попадал в журнал как доставленный. Исправлено на ленивый разбор (первое `status=` после получателя).
-
-**Модель:** Haiku (механическая чистка комментариев), Sonnet (точечные правки)
-
----
-
-## 4. Полнота документации и соответствие коду
-
-### Сильные стороны
-
-- **[architecture.md](architecture.md)** — as-built source of truth; маршруты, процессы, persistence совпадают с кодом.
-- **[security.md](security.md)** — чеклист + принятые риски; каждый риск привязан к коду.
-- **[product.md](product.md)** — границы v1.0/out-of-scope чёткие.
-- **Regression guard:** [`cmd/panel/envdoc_test.go`](../cmd/panel/envdoc_test.go) — env vars в README = `loadConfig`.
-- **CHANGELOG** в формате Keep a Changelog.
-
-### Расхождения docs ↔ code
-
-| Проблема | Где | Реальность |
-|----------|-----|------------|
-| Image tag `0.1.0` vs «v1.0» | [`deploy/docker-compose.yml`](../deploy/docker-compose.yml) vs README | **Открыто:** roadmap § v1.x — bump в релизном коммите вместе с git-тегом |
-| Quick start URLs | README | **Закрыто:** Codeberg уходит как публичная площадка, единственный дом проекта — GitHub; вместе с URL переехал и путь Go-модуля (`github.com/mixeme/selfpost`) |
-| `docs/logo` | roadmap | **Закрыто** (Фаза 1): каталог отсутствует, критерию удовлетворяет |
-| `docs/specification.md` | documentation-plan D9 | **Закрыто:** файл остаётся в `docs/archive/` как история, но ссылок на него из кода больше нет — все «spec N.x» заменены на живые документы |
-| Phase language в коде | 50+ файлов | **Закрыто** (Фаза 1) |
-| RU/EN split | progress, roadmap, implementation-plan (RU) vs README/architecture (EN) | Намеренно, но барьер для EN-only contributors; снимается вместе с `CONTRIBUTING.md` — перенесено в [roadmap.md](roadmap.md) § 2.x |
-
-### Комментирование кода
-
-- **Высокое качество** в security-critical paths.
-- **Среднее** в CRUD handlers (делегируют в services — acceptable).
-- **Выполнено:** ссылки на архивную спецификацию убраны из кода целиком — не только «spec 7.x», но и «spec 4/5/6/8/9», которые страдали ровно тем же (указывали в документ, помеченный «не источник истины»). Каждая заменена на живой документ, владеющий темой: [architecture.md](architecture.md) с указанием секции, [product.md](product.md), [security.md](security.md) или README. Секция указывается там, где документ большой (architecture.md, README); для короткого `product.md` — только файл.
-
-**Модель:** Sonnet (docs sync)
-
----
-
-## 5. Читаемость и поддерживаемость
-
-### Для кого код читаем
-
-- **Go-разработчик со знанием SMTP/Postfix** — да, без проблем.
-- **Новичок без почтового бэкграунда** — потребуется [architecture.md](architecture.md) + README.
-
-### Факторы, помогающие поддержке
-
-- Предсказуемая структура handler → service → store.
-- Embedded templates (`//go:embed`) — один binary, нет внешних assets.
-- Makefile targets: `vet`, `test`, `build`, `e2e`.
-- E2E suite покрывает happy path + negatives.
-
-### Факторы, затрудняющие поддержку
-
-- Phase-номера в комментариях без контекста.
-- Dual cookie names (`__Host-` vs plain) — хорошо документировано, но неочевидно.
-- HTMX fragment polling — нужно понимать SSR + partial updates.
-- Dev loop: Windows local edit → SSH to Debian server ([progress.md](progress.md)) — не стандартный `go run`.
-
-### Рекомендации
-
-| # | Действие | Модель |
-|---|----------|--------|
-| M1 | Cleanup phase-комментариев → «as-built» language | Haiku |
-| M2 | Добавить `CONTRIBUTING.md` (dev loop, model routing, commit protocol) — опционально v1.x | Sonnet |
-| M3 | Consolidated doc index в README (ссылки на все docs/) | Sonnet |
-
----
-
-## 6. Логические ошибки и риски
-
-### Критичных багов не обнаружено
-
-E2E покрывает: bootstrap, SMTP AUTH, DKIM, send-log lifecycle, negatives (relay, sender mismatch, L1/L2 limits, milter fail-open, hostname gate, session survive restart).
-
-### Принятые операционные gap'ы (не баги, но важно знать)
-
-| Gap | Описание | Документировано |
-|-----|----------|-----------------|
-| Send-log `queued` forever | **Закрыто для рестарта:** Фаза 3 — offset персистится (`logtail_state`), хвост дочитывается. Остаётся пересоздание контейнера: `mail.log` не в `/data` | [security.md](security.md), [roadmap.md](roadmap.md) |
-| CSRF without tokens | POST без Origin/Sec-Fetch-Site пропускается | [security.md](security.md) |
-| Fail-open L2 rate limit | DB error → mail проходит | [`internal/milter/ratelimit.go`](../internal/milter/ratelimit.go) |
-| Shallow SPF check | Не следует `include:`/`redirect=` | README, `internal/dnscheck/spf.go` |
-| Plaintext backup/export at rest | DKIM-ключи, SASL, пароли приложений в cleartext `.tar.gz`/`.json` | **Закрыто:** R13 — опциональное шифрование (`.spbk`/`.spde`); открытый вариант остаётся умолчанием, риск переформулирован в [security.md](security.md) |
-
-**Не риск (решение оператора):** «Session resurrection from backup» — снято из [security.md](security.md).
-
-### Потенциальные логические нюансы (низкий приоритет)
-
-1. **Rate limit race:** `CountMessages` + `InsertQueued` не в одной транзакции — при высокой нагрузке возможен overshoot на 1 сообщение. Для differentiated limits — acceptable.
-2. **Domain export с plaintext passwords** ([`internal/domain/transfer.go`](../internal/domain/transfer.go)) — by design; **mitigation:** R13 (optional password encryption).
-3. **`macro()` dual lookup** ([`internal/milter/milter.go`](../internal/milter/milter.go)) — workaround для Postfix/go-milter; permanent, not a bug.
-
-### Рекомендации
-
-| # | Действие | Приоритет | Модель |
-|---|----------|-----------|--------|
-| L1 | **Предрелизный security review** (§ D) — обязательный гейт | **P0** | **Fable** |
-| L2 | **Шифрование бэкапа и экспорта домена** (R13) — optional, checkbox + password — **выполнено** | P1 | **Opus** + Sonnet |
-| L3 | Send-log gap mitigation — **выполнено** (persist offset, Фаза 3) | P2 | Opus |
-| L4 | Rate limit count+insert — **выполнено** (учёт «в полёте», Фаза 3; транзакция как таковая неприменима) | P3 | Opus |
-
----
-
-## 7. Legacy-код и миграции
-
-### SQL-миграции
-
-- [`0001_init.sql`](../internal/store/migrations/0001_init.sql) — initial schema
-- [`0002_sessions.sql`](../internal/store/migrations/0002_sessions.sql) — sessions (plan B.1)
-- [`0003_logtail_state.sql`](../internal/store/migrations/0003_logtail_state.sql) — log-tailer read offset (Фаза 3)
-- Механизм: `PRAGMA user_version`, embedded FS, transactional apply — **чистый**, без legacy branches в коде.
-
-### Архивная документация
-
-- [`docs/archive/specification-v1.0.md`](archive/specification-v1.0.md) — historical; помечен «не источник истины».
-- **Рекомендация:** оставить в archive; в коде заменить «spec 7.x» на актуальные doc-ссылки.
-
-### Legacy patterns в runtime
-
-| Элемент | Статус | Действие |
-|---------|--------|----------|
-| Phase 0–14 comments | Historical noise | Cleanup (Haiku) |
-| `macro()` brace workaround | Permanent Postfix compat | Оставить, уже документировано |
-| Legacy charset (windows-1251) in milter | Keep raw header on decode fail | Оставить |
-| In-memory sessions | **Удалено** (B.1 → SQLite) | Done |
-| `copytruncate` log rotation | **Заменено** (B.2 → rename+reload) | Done |
-
-**Перспектива удаления:** единственный кандидат на cleanup — **phase-комментарии** и **архив spec** (оставить файл, убрать ссылки из кода).
-
----
-
-## 8. GUI: «костыли» и оптимизация компоновки
-
-### Стек
-
-Go `html/template` + HTMX polling + [`panel.css`](../internal/web/static/panel.css) + [`panel.js`](../internal/web/static/panel.js). **Нет React/Vue** — минимальный footprint.
-
-### Поиск маркеров долга
-
-**TODO / FIXME / HACK / kostyl — 0 вхождений** по всему репозиторию.
-
-### Осознанные компромиссы (не костыли)
-
-| Компромисс | Файл | Обоснование |
-|------------|------|-------------|
-| External CSS/JS only (no inline) | `panel.css`, `panel.js` | CSP `default-src 'self'` |
-| HTMX `includeIndicatorStyles: false` | `layout.html` | Avoid CSP exception |
-| Block layout for applications (not table) | `panel.css` | 4 cols + 6 controls don't fit 48rem |
-| Two-row nav | `panel.css` | Session block vs page links width |
-| Page-specific max-width (48/64/24rem) | `panel.css` | Monitoring vs forms |
-| Subject ellipsis via inner `` | `panel.css` | `max-width` on `
` is advisory |
-| Dark mode `!important` overrides | `panel.css` | Override specificity without restructuring |
-| HTMX poll excluded from session renewal | `middleware.go` | Idle timeout semantics |
-| Dual cookie names | `handlers_auth.go` | `__Host-` requires Secure |
-
-### Возможные оптимизации GUI
-
-| # | Оптимизация | Effort | Модель |
-|---|-------------|--------|--------|
-| G1 | HTMX polling only when tab visible (`document.visibilityState`) | Low | Sonnet |
-| G2 | CSS custom properties для dark mode вместо `!important` cascade | Medium | Sonnet |
-| G3 | Consolidate duplicate `main { max-width }` rules | Trivial | Haiku |
-| G4 | `hx-trigger="every 5s"` → adaptive interval (5s active, 30s idle) | Low | Sonnet |
-
-**Вердикт:** GUI **не содержит костылей**; все workarounds документированы и оправданы CSP/layout constraints.
-
----
-
-## 9. Слабо задокументированные спорные решения
-
-### Хорошо задокументированные (security.md + code comments)
-
-- Fail-open journal-milter vs fail-closed OpenDKIM
-- CSRF via Origin (no tokens)
-- `__Host-` cookie + duplicate detection
-- Plaintext passwords in domain export → **mitigation:** R13
-- Send-log queued gap
-- SQLite single connection
-- No in-container TLS
-
-### Требуют усиления документации
-
-| Решение | Текущее состояние | Рекомендация |
-|---------|-------------------|--------------|
-| **Почему нет CSRF-токенов** (только Origin check) | Частично в security.md | Добавить ADR-style параграф в security.md |
-| **Почему panel HTTP, не HTTPS** | README + architecture | Достаточно |
-| **Почему chroot disabled в Postfix** | architecture.md | Достаточно |
-| **Порядок supervisord** (opendkim → panel → postfix) | architecture.md | Достаточно |
-| **Почему log-tailer не persist offset** | roadmap optional | Явно в architecture.md § known limitations |
-| **Import domain с plaintext password** | transfer.go comment | Достаточно для v1; шифрование — R13 |
-| **Plaintext full backup / domain export at rest** | handlers_backup.go | **R13:** optional AES-GCM envelope (`.spbk`/`.spde`) |
-
-**Модель:** Sonnet (дополнить security.md / architecture.md)
-
----
-
-## 10. Прочие предложения по оптимизации
-
-### Pre-release (блокеры)
-
-| # | Задача | Модель | Ref |
-|---|--------|--------|-----|
-| R0 | Security review diff v1.0.0→HEAD + checklist 7.6 — **выполнено** (2026-08-06) | **Fable** | implementation-plan § D |
-| R1 | Bump image tag in compose при git tag — **открыто**, делается в релизном коммите вместе с тегом | Sonnet | roadmap § v1.x |
-| R2 | ~~Codeberg URLs в README Quick start~~ — **снято**: Codeberg уходит, GitHub остаётся единственной площадкой. Вместо перевода ссылок *на* Codeberg сделан обратный переезд: URL, лицензионные шапки SVG/HTML и путь Go-модуля | Sonnet | — |
-
-### v1.x polish (не блокеры)
-
-| # | Задача | Модель |
-|---|--------|--------|
-| R3 | Cleanup phase-комментариев (50+ files) — **выполнено** (Фаза 1) | Haiku |
-| R4 | Fix stale comment in handlers_domains.go — **выполнено** (Фаза 1) | Haiku |
-| R5 | docs/logo: создать или удалить из roadmap — **выполнено** (Фаза 1) | Haiku |
-| R6 | GUI: visibility-aware HTMX polling — **выполнено** (Фаза 2) | Sonnet |
-| R7 | CONTRIBUTING.md — **перенесено в 2.x** ([roadmap.md](roadmap.md)): у проекта один разработчик и нет внешнего потока PR, документ был бы без аудитории | Sonnet |
-| R8 | ADR для CSRF policy — **выполнено** (Фаза 1, [security.md](security.md)) | Sonnet |
-| R13 | Шифрование бэкапа и экспорта домена (checkbox + password) — **выполнено** | **Opus** + Sonnet |
-
-### v2.x (roadmap, не начинать без согласования)
-
-| # | Задача | Модель |
-|---|--------|--------|
-| R9 | Inbound relay (Phase O1) | **Opus** |
-| R10 | Domain-admin role | Opus |
-| R11 | Send-log gap fix (persist offset) — **выполнено в Фазе 3**, из 2.x снято | Opus |
-| R12 | Split internal/web subpackages | Sonnet/Opus |
-
-### CI/infra
-
-- E2E готов (`test/e2e/`); release workflow matrix amd64/arm64 — **хорошо**.
-- `go vet` + `go test` на push — **достаточно** для v1.x.
-- Рекомендация: добавить `gofmt -l` check в CI (progress.md упоминает как manual step) — **выполнено** (Фаза 1, [.github/workflows/test.yml](../.github/workflows/test.yml)).
-
-**Модель:** Haiku (CI one-liner)
-
----
-
-## План реализации (приоритизированный)
-
-### Фаза 0 — Гейт релиза (P0)
-
-Содержательная часть закрыта 2026-08-06; остались только шаги самой резки
-версии, которые делаются по явной команде оператора.
-
-1. Fable: `/security-review` по diff v1.0.0...HEAD — **выполнено**
-2. Fable: ручной проход security.md checklist § 7.6 — **выполнено**
-3. Каждая finding → fix ИЛИ запись в security.md — **выполнено** (одна правка
- defence-in-depth, принятые риски не пополнились)
-4. `make e2e` зелёный — **выполнено** (dev-сервер)
-5. ~~Codeberg URLs~~ — **снято**, см. R2: переезд сделан в обратную сторону, на
- GitHub. Остаётся bump тега образа в compose — **открыто**, в релизном коммите
-6. Git tag vX.Y.Z — **открыто**, [roadmap.md](roadmap.md) § v1.x
-
-### Фаза 1.5 — Шифрование резервных копий (P1, v1.x) — **выполнено 2026-08-06**
-
-Реализовано как спланировано: `internal/secretfile` (E1) → `selfpost-backup`
-+ панель (E2) → экспорт/импорт домена (E3) → UI-чекбокс (E4) → docs (E5).
-Отличия от плана: конверт потоковый (64 KiB чанки AES-256-GCM с AAD
-`header+counter+last`), а не одноблочный, иначе полный бэкап пришлось бы
-держать в памяти целиком; манифест остался внутри tar, то есть внутри
-шифротекста, как и планировалось; в CLI добавлен режим `-decrypt` — без него
-зашифрованный бэкап нечем распаковать при restore. Детали —
-[progress.md](progress.md), CHANGELOG `[Unreleased]`.
-
-**Проблема:** полный бэкап и экспорт домена содержат DKIM-ключи, SASL-креды и plaintext-пароли приложений; сейчас `.tar.gz` / `.json` без шифрования.
-
-**Решение:** опциональное шифрование паролем (чекбокс «Encrypt with password»; поля password + confirm — только при включённой галочке; переключение в `panel.js`, без inline script).
-
-| Арtefact | Cleartext | Encrypted |
-|----------|-----------|-----------|
-| Полный бэкап | `.tar.gz` | `.spbk` (**S**elf**P**ost **B**ac**k**up) |
-| Экспорт домена | `.json` | `.spde` (**S**elf**P**ost **D**omain **E**xport) |
-
-**Формат:** magic `SELFPOST1`, type byte, scrypt KDF, AES-256-GCM; manifest внутри ciphertext.
-
-**Задачи:** E1 crypto envelope → E2 backup/CLI → E3 domain export/import → E4 UI (checkbox) → E5 docs + e2e. **Модель:** Opus (crypto), Sonnet (UI/docs).
-
-### Фаза 1 — Doc/code hygiene (P1) — **выполнено 2026-08-06**
-
-1. Haiku: массовая замена phase-комментариев (mechanical pass) — **сделано**
-2. Haiku: fix handlers_domains.go stale comment — **сделано**
-3. Sonnet: ADR CSRF в security.md — **сделано**
-4. Sonnet: known limitations § в architecture.md (send-log gap) — **уже было**
- в § Log tailer, правка не потребовалась
-5. Haiku: docs/logo resolve — **сделано**
-6. Haiku: gofmt CI check — **сделано**
-
-Добор той же фазы (2026-08-06, отдельным проходом): ссылки на архивную
-спецификацию убраны из кода целиком (§ 4), добавлена диаграмма слоёв в
-architecture.md (A2), расширен `TestParseDelivery` (§ 3) — последнее вскрыло
-реальный баг разбора `status=`.
-
-### Фаза 2 — GUI polish (P2, optional) — **выполнено 2026-08-06**
-
-1. Sonnet: HTMX visibility-aware polling (panel.js) — **сделано иначе**:
- фильтр повешен на `htmx:beforeRequest`, а не на встроенный фильтр триггера
- htmx — тот вычисляется через `new Function`, что CSP панели без
- `unsafe-eval` молча ломает
-2. Sonnet: CSS custom properties для dark mode — **сделано**
-3. Haiku: consolidate main max-width rules — **сделано**
-
-### Фаза 3 — Operational improvements (P2–P3, optional) — **выполнено 2026-08-06**
-
-1. Opus: send-log read offset persistence — **сделано**: `logtail_state`
- (миграция `0003`) хранит offset + отпечаток головы лога; при совпадении
- отпечатка чтение продолжается, при несовпадении файл читается с начала,
- первый запуск (записи нет) — с конца, как раньше.
-2. Opus: rate limit count transaction wrap — **сделано иначе**: буквальная
- транзакция невозможна, count живёт на MAIL FROM, insert — на end-of-message,
- это разные стадии SMTP-транзакции. Overshoot закрыт учётом сообщений «в
- полёте» (`internal/milter/inflight.go`): к счёту из БД добавляются
- резервации, взятые прошедшими проверку сессиями и снимаемые после записи в
- send-log, на ABORT или по TTL 10 минут.
-
-Остаток по send-log (не закрывается персистом offset): при пересоздании
-контейнера `mail.log` теряется вместе с ним — принятый риск в
-[security.md](security.md).
-
----
-
-## Маршрутизация моделей (сводная таблица)
-
-| Тип работы | Модель | Обоснование |
-|------------|--------|-------------|
-| Security review (не authorship) | **Fable** | Независимость от автора (Opus) |
-| Security fixes, infra, Postfix | **Opus** | Risk-critical |
-| UI, docs, CSS, templates | **Sonnet** | Баланс качества и скорости |
-| Mechanical cleanup, CI, trivial fixes | **Haiku** | Минимальный scope |
-| Inbound relay 2.x | **Opus** | Open relay risk |
-
-*Источник правил:* [progress.md](progress.md) § «Модель по типу работы», [development.md](development.md) § Agent rules.
diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md
index e094d96..eb8c3c2 100644
--- a/docs/implementation-plan.md
+++ b/docs/implementation-plan.md
@@ -4,9 +4,8 @@
безопасности (§ D) выполнена 2026-08-06, релизный гейт (e2e + ревизия) открыт.
B.1–B.3 и C.4 закрыты — as-built в [architecture.md](architecture.md),
e2e/CI в [development.md](development.md), принятые риски в
-[security.md](security.md). Комплексное рецензирование кодовой базы и план
-доработок — [code-review.md](code-review.md). Текущее состояние и следующий
-шаг: [progress.md](progress.md). Объём 2.x.x — [roadmap.md](roadmap.md).
+[security.md](security.md). Текущее состояние и следующий шаг:
+[progress.md](progress.md). Объём 2.x.x — [roadmap.md](roadmap.md).
**Основа:** [product.md](product.md) v1.0.
diff --git a/docs/progress.md b/docs/progress.md
index 82f3ad1..df140ac 100644
--- a/docs/progress.md
+++ b/docs/progress.md
@@ -5,7 +5,6 @@
Линия 2.x.x (входящий релей, роль администратора домена): [roadmap.md](roadmap.md).
Продукт: [product.md](product.md), устройство: [architecture.md](architecture.md).
Процесс разработки: [development.md](development.md). Принятые риски безопасности: [security.md](security.md).
-Рецензирование кодовой базы и план доработок: [code-review.md](code-review.md).
История релизов: [CHANGELOG.md](../CHANGELOG.md).
История сделанного по фазам (0→13, все закрыты) — в `git log` и в CHANGELOG, здесь не дублируется.
@@ -48,14 +47,14 @@
- **B.3 реализован** (не выкачен на прод): `build/entrypoint.sh` проверяет `SELFPOST_HOSTNAME` до `postfix-config.sh` и до `supervisord` — при пустом значении `exit 1` с развёрнутым текстом ошибки (что это за имя, почему обязательно, пример, где задаётся); плюс синтаксическая проверка через `case`: минимум одна точка, без схемы/порта/пробелов (`*://*`, `*:*`, пробел/таб — тот же класс тихого спам-отказа, что и пустое значение). `saslRealm()` и fallback в `postfix-config.sh` не тронуты — после гейта эти ветки мертвы. Заодно отмечена обязательность переменной в `README.md` и `deploy/.env.example`. Проверено на стенде (`selfpost.mixfed.ru`, отдельный образ `selfpost:b3test`, cap-list как в поставляемом compose): без переменной — `exit 1` с ожидаемым текстом, без бесконечного тихого retry; `https://mail.example.com:465` и `localhost` отклонены с понятными сообщениями; валидный `mail.example.com` — обычный старт, все процессы supervisord поднимаются. `go vet`/`go test ./...` чистые.
- **C.4 реализован** (не выкачен на прод — это CI/тестовая инфраструктура, а не образ): герметичный контейнерный e2e отдельным Go-модулем `test/e2e/` (свой `go.mod`, не подхватывается `go test ./...` основного модуля) поверх поставляемого `deploy/docker-compose.yml` плюс `test/e2e/compose.override.yml` (самоподписанный сертификат, `PANEL_COOKIE_SECURE=false`, `SELFPOST_HOSTNAME=mail.e2e.test`, высокие порты `20465/20587/20080`, изолированный compose-проект `selfpost-e2e`, свой `--project-directory` — прод на том же хосте не задет). Герметичная почта: CoreDNS (`test/e2e/dns/Corefile` — авторитетна только для `e2e.test`, `file`-плагин с саб-директивой `reload` перечитывает `db.zone` по mtime, без сигналов) плюс `smtp-sink` из пакета postfix (`test/e2e/sink/`) как sink-MX. Сценарий (`test/e2e/*_test.go`): старт контейнера → все supervisord-программы `RUNNING` (`postfix-reload` — `STOPPED`) → токен из `/data/setup-token` → setup → login → добавление домена → DKIM-запись **скраплена со страницы панели** и опубликована в фейковую зону → добавление приложения → SMTP AUTH на 465 → письмо на sink → DKIM-подпись проверена (`go-msgauth/dkim` с кастомным `LookupTXT` через CoreDNS) против ключа **из DNS**, не из панели напрямую → send-log `queued → sent`. Негативы: без AUTH, relay на чужой домен без AUTH, sender/login mismatch (`reject_sender_login_mismatch` репортится Postfix'ом на RCPT, не MAIL — `smtpd_delay_reject=yes` по умолчанию), L1-лимит (anvil, override `RATE_LIMIT_MESSAGES_PER_IP=50` — специально высокий, чтобы остальные под-тесты не расходовали общий бюджет по IP раньше времени; сам тест шлёт до 60 раз, ждёт отказа), L2-лимит через панель (домен/приложение → `rejected`-строка в send-log), fail-open journal-milter'а (`supervisorctl stop panel`, письмо всё равно принято, контейнер жив), пустой/синтаксически неверный `SELFPOST_HOSTNAME` (отдельный один-разовый контейнер, не общий стенд), сессия переживает `docker restart` (плюс явное ожидание готовности smtps-порта после рестарта — панель и Postfix поднимаются независимо). `make e2e` — локальный/dev-server прогон. Найдено и исправлено по ходу стендовой проверки: `reload` — саб-директива `file`-плагина CoreDNS, а не отдельный топ-левел плагин (топ-левел `reload` следит за самим Corefile, не за зоной); `docker compose build.context` резолвится относительно `--project-directory`, а не относительно файла, где объявлен; `smtp-sink` отказывается стартовать от root без `-u`; `html/template` эскейпит `+` в `+` даже в тексте — скрапер значений со страницы обязан `html.UnescapeString`; проверки состояния сразу после `up`/`restart` должны поллиться, а не разово опрашиваться (supervisord/postfix поднимаются не мгновенно). **Проверено на dev-сервере (`selfpost.mixfed.ru`)**: `make e2e` — зелёный (`go vet`/`gofmt -l` тоже чистые в обоих модулях). `release.yml` переработан: job `prepare` (версия из тега) → матрица `[ubuntu-latest, ubuntu-24.04-arm]` — каждая нативно собирает образ (`--load`), прогоняет e2e, пушит тег `X.Y.Z-amd64`/`X.Y.Z-arm64` → job `merge` — `docker buildx imagetools create` в единый тег `X.Y.Z`; `setup-qemu-action` убран. Не проверено вживую (нельзя без реального тега): сам workflow на GitHub Actions — синтаксис вычитан, логика идентична локальному `make e2e` пути.
- **Документация:** план D1–D9 закрыт ([documentation-plan.md](documentation-plan.md) — только метод и правила поддержки). Хвост v1.x — [roadmap.md](roadmap.md) § «v1.x — хвост документации и деплоя»; из него остался только бамп тега образа (Quick start и `docs/logo` закрыты).
-- **Рецензирование кодовой базы** (2026-08-05): [code-review.md](code-review.md) — 10 разделов (архитектура, качество, docs, GUI, legacy, риски), приоритизированный план реализации и маршрутизация моделей. Критичных багов не найдено; блокер релиза — § D ниже.
+- **Рецензирование кодовой базы** (2026-08-05, `522425a`): 10 разделов (архитектура, качество, docs, GUI, legacy, риски) плюс приоритизированный план доработок фазами 0–3. Критичных багов не найдено; единственным блокером релиза названа § D. **План выполнен целиком** (см. записи ниже), поэтому сам документ `docs/code-review.md` удалён — незакрытые пункты унесены в [roadmap.md](roadmap.md) (разбиение `internal/web`, индекс документации в README, адаптивный интервал опроса, `CONTRIBUTING.md`), остальное либо сделано, либо уже описано в architecture.md / security.md / комментариях кода. Текст ревизии — в git-истории.
- **§ D выполнен (2026-08-06):** предрелизная ревизия безопасности моделью Fable — диф от аудита v1.0 (Фаза 11, `bd64e80`) до HEAD + полный проход по чек-листу [security.md](security.md) (бывшее ТЗ 7.6). Эксплуатируемых находок нет; одна правка defence-in-depth (`--` перед логином в argv `saslpasswd2`, `internal/app/sasl.go` + тест). Принятые риски не пополнились. Детали — [implementation-plan.md](implementation-plan.md) § D и CHANGELOG `[Unreleased]/Security`. Локально `go vet`/`go test ./internal/app/...` чистые; падения `internal/domain` (`TestWriteLoadPrivateKeyRoundtrip`, `TestRenderTables`) и `internal/logtail` (`TestFollowTailsAndRotates`) — Windows-специфика (права файлов/`\` в путях/rename открытого файла), на Linux CI зелено.
-- **Фаза 1 выполнена (2026-08-06)** ([code-review.md](code-review.md) § Фаза 1 — doc/code hygiene, P1): cleanup ~30 stale «Phase N» комментариев в коде и shell-скриптах; исправлен stale-комментарий в `handlers_domains.go`; ADR CSRF (Origin vs токены) добавлен в [security.md](security.md); known-limitations по log-tailer уже был в [architecture.md](architecture.md) § Log tailer — отдельного действия не потребовалось; `docs/logo` в [roadmap.md](roadmap.md) закрыт (каталога нет, критерию соответствует); `gofmt -l` добавлен в CI (`.github/workflows/test.yml`). `gofmt`/`go vet`/`go test ./...` чистые в обоих модулях (dev-server).
-- **Фаза 1.5 выполнена (2026-08-06)** ([code-review.md](code-review.md) § Фаза 1.5 — шифрование резервных копий, P1): новый пакет `internal/secretfile` — конверт `magic SELFPOST1 | type | scrypt-параметры | salt | nonce-prefix` + поток 64 KiB чанков AES-256-GCM, каждый с AAD `header+counter+last`, поэтому обрезка, перестановка и подмена не открываются (стриминг в обе стороны — полный бэкап не держится в памяти). Панель: чекбокс «Encrypt with a password» в форме полного бэкапа и экспорта домена (общий партиал `templates/encrypt_fields.html`, показ/очистка полей — `panel.js`, без inline-скриптов), импорт домена принимает `.spde` (шифрование определяется по magic, не по расширению) с полем пароля. CLI `selfpost-backup`: пишет `.spbk` при заданном пароле и умеет `-decrypt` (иначе зашифрованный бэкап нечем распаковать при restore); пароль — только `SELFPOST_BACKUP_PASSWORD` / `-password-file`, никогда argv. Умолчание не изменилось: галочка снята — прежние `.tar.gz` / `.json` байт в байт. Тесты: round-trip по размерам (0, границы чанка, несколько чанков), неверный пароль, обрезка, перестановка чанков, порча байта, чужие KDF-параметры; валидация формы пароля; round-trip CLI create→decrypt→tar. Docs: README § *Encrypting a backup or export*, [security.md](security.md) § «Резервная копия и экспорт домена» + принятый риск (шифрование опционально), [architecture.md](architecture.md) § Persistence. `gofmt`/`go vet`/`go test ./...` чистые (кроме известных Windows-падений `internal/domain`, `internal/logtail`). E2E-сценарий не добавлялся: в `test/e2e/` бэкапа не было и раньше, а прогнать новый тест локально нечем (нет Docker) — кандидат при следующем прогоне на dev-сервере.
-- **Фаза 2 выполнена (2026-08-06)** ([code-review.md](code-review.md) § Фаза 2 — GUI polish, P2): опрос мониторинговых страниц не уходит на сервер, пока вкладка скрыта — фильтр повешен на `htmx:beforeRequest` в `panel.js`, а не на встроенный в htmx фильтр триггера (тот вычисляется через `new Function`, что CSP панели `default-src 'self'` без `unsafe-eval` молча ломает); тёмная тема переписана с каскада `!important` на переопределение CSS-переменных в одном блоке `prefers-color-scheme: dark`; дублирующее правило `main { max-width }` сведено к одному базовому плюс задокументированные постраничные оверрайды. Только CSS/JS, поведения сервера не касается; вживую не проверялось (нет Docker локально) — кандидат на следующий прогон на dev-сервере.
-- **Фаза 3 выполнена (2026-08-06)** ([code-review.md](code-review.md) § Фаза 3 — operational improvements, P2–P3): (1) log-tailer сохраняет позицию чтения — таблица `logtail_state` (миграция `0003`, `internal/store/logtail.go`) хранит offset + отпечаток первых 512 байт лога, `internal/logtail/offset.go` решает откуда стартовать: отпечаток совпал → продолжаем с offset (дочитывается хвост, написанный пока панель лежала); не совпал (лог сменился/пересоздан) → читаем файл с начала (повторный разбор безвреден, `UpdateStatus` идемпотентен); записи нет вовсе (первый запуск) → с конца, как раньше. Запись offset — не чаще раза в 5 с, плюс форс при ротации и на выключении; сохраняется позиция *потреблённых* байт (минус недочитанная частичная строка). (2) L2-лимит перестал промахиваться при параллельных сессиях: между проверкой на MAIL FROM и вставкой строки на end-of-message сообщение не видно в БД, поэтому N одновременных сессий пропускали друг друга — теперь к счёту из БД добавляются «в полёте» (`internal/milter/inflight.go`, общий на процесс реестр резерваций); резервация освобождается после записи в send-log, на ABORT и по TTL 10 минут (у go-milter нет колбэка на закрытие соединения, а вечная резервация — это fail-closed-дрейф, которого у лимитера быть не должно). Транзакция «count+insert», как предлагал review, невозможна буквально: эти два шага разнесены по разным стадиям SMTP-транзакции. Тесты: restart/rotation-resume для tailer'а, четыре сценария резерваций для лимита. `gofmt`/`go vet` чистые; `go test ./...` — падения только известные Windows-специфичные (`internal/domain`, `TestFollowTailsAndRotates`). Не проверено на стенде (нет Docker локально) — кандидат на следующий прогон на dev-сервере.
-- **Добор по code-review выполнен (2026-08-06):** (1) проект переехал на единственную площадку — GitHub (Codeberg уходит): вместе с URL, лицензионными шапками SVG/HTML и docs переехал путь Go-модуля на `github.com/mixeme/selfpost` (`go.mod`, `test/e2e/go.mod`, все импорты, `MODULE` в Makefile, `-ldflags` в Dockerfile и development.md) — оставлять импорты на исчезающем хосте нельзя, `go get`/`go install` сломались бы; (2) ссылки на архивную спецификацию убраны из кода целиком — не только «spec 7.x» из § 4 ревью, но и «spec 4/5/6/8/9», страдавшие тем же, каждая заменена на живой документ с секцией там, где документ большой; (3) [architecture.md](architecture.md) § Code layers — диаграмма слоёв (A2); (4) `TestParseDelivery` расширен экзотикой mail.log — и **вскрыл реальный баг**: шаблон брал `status=` жадно, то есть последнее вхождение в строке, а Postfix дописывает ответ удалённого сервера дословно, поэтому отказ с `status=sent` в тексте ответа попадал в журнал как доставленный (исправлено на ленивый разбор); (5) R7 (`CONTRIBUTING.md`) перенесён в 2.x, R1 и git-тег оставлены в [roadmap.md](roadmap.md) § v1.x. `gofmt`/`go vet` чистые в обоих модулях, `go test ./...` — падения только известные Windows-специфичные (`internal/domain`, `TestFollowTailsAndRotates`). На стенде не проверялось (нет Docker локально).
-- **Дальше:** релизный гейт (Фаза 0) закрыт по существу — e2e C.4 и ревизия § D пройдены; остаются только шаги, которые делаются в момент резки версии (бамп тега образа в compose, git tag) по явной команде пользователя. Все polish-фазы из [code-review.md](code-review.md) (1, 1.5, 2, 3) закрыты.
+- **Фаза 1 плана ревизии выполнена (2026-08-06)** (doc/code hygiene, P1): cleanup ~30 stale «Phase N» комментариев в коде и shell-скриптах; исправлен stale-комментарий в `handlers_domains.go`; ADR CSRF (Origin vs токены) добавлен в [security.md](security.md); known-limitations по log-tailer уже был в [architecture.md](architecture.md) § Log tailer — отдельного действия не потребовалось; `docs/logo` в [roadmap.md](roadmap.md) закрыт (каталога нет, критерию соответствует); `gofmt -l` добавлен в CI (`.github/workflows/test.yml`). `gofmt`/`go vet`/`go test ./...` чистые в обоих модулях (dev-server).
+- **Фаза 1.5 плана ревизии выполнена (2026-08-06)** (шифрование резервных копий, P1): новый пакет `internal/secretfile` — конверт `magic SELFPOST1 | type | scrypt-параметры | salt | nonce-prefix` + поток 64 KiB чанков AES-256-GCM, каждый с AAD `header+counter+last`, поэтому обрезка, перестановка и подмена не открываются (стриминг в обе стороны — полный бэкап не держится в памяти). Панель: чекбокс «Encrypt with a password» в форме полного бэкапа и экспорта домена (общий партиал `templates/encrypt_fields.html`, показ/очистка полей — `panel.js`, без inline-скриптов), импорт домена принимает `.spde` (шифрование определяется по magic, не по расширению) с полем пароля. CLI `selfpost-backup`: пишет `.spbk` при заданном пароле и умеет `-decrypt` (иначе зашифрованный бэкап нечем распаковать при restore); пароль — только `SELFPOST_BACKUP_PASSWORD` / `-password-file`, никогда argv. Умолчание не изменилось: галочка снята — прежние `.tar.gz` / `.json` байт в байт. Тесты: round-trip по размерам (0, границы чанка, несколько чанков), неверный пароль, обрезка, перестановка чанков, порча байта, чужие KDF-параметры; валидация формы пароля; round-trip CLI create→decrypt→tar. Docs: README § *Encrypting a backup or export*, [security.md](security.md) § «Резервная копия и экспорт домена» + принятый риск (шифрование опционально), [architecture.md](architecture.md) § Persistence. `gofmt`/`go vet`/`go test ./...` чистые (кроме известных Windows-падений `internal/domain`, `internal/logtail`). E2E-сценарий не добавлялся: в `test/e2e/` бэкапа не было и раньше, а прогнать новый тест локально нечем (нет Docker) — кандидат при следующем прогоне на dev-сервере.
+- **Фаза 2 плана ревизии выполнена (2026-08-06)** (GUI polish, P2): опрос мониторинговых страниц не уходит на сервер, пока вкладка скрыта — фильтр повешен на `htmx:beforeRequest` в `panel.js`, а не на встроенный в htmx фильтр триггера (тот вычисляется через `new Function`, что CSP панели `default-src 'self'` без `unsafe-eval` молча ломает); тёмная тема переписана с каскада `!important` на переопределение CSS-переменных в одном блоке `prefers-color-scheme: dark`; дублирующее правило `main { max-width }` сведено к одному базовому плюс задокументированные постраничные оверрайды. Только CSS/JS, поведения сервера не касается; вживую не проверялось (нет Docker локально) — кандидат на следующий прогон на dev-сервере.
+- **Фаза 3 плана ревизии выполнена (2026-08-06)** (operational improvements, P2–P3): (1) log-tailer сохраняет позицию чтения — таблица `logtail_state` (миграция `0003`, `internal/store/logtail.go`) хранит offset + отпечаток первых 512 байт лога, `internal/logtail/offset.go` решает откуда стартовать: отпечаток совпал → продолжаем с offset (дочитывается хвост, написанный пока панель лежала); не совпал (лог сменился/пересоздан) → читаем файл с начала (повторный разбор безвреден, `UpdateStatus` идемпотентен); записи нет вовсе (первый запуск) → с конца, как раньше. Запись offset — не чаще раза в 5 с, плюс форс при ротации и на выключении; сохраняется позиция *потреблённых* байт (минус недочитанная частичная строка). (2) L2-лимит перестал промахиваться при параллельных сессиях: между проверкой на MAIL FROM и вставкой строки на end-of-message сообщение не видно в БД, поэтому N одновременных сессий пропускали друг друга — теперь к счёту из БД добавляются «в полёте» (`internal/milter/inflight.go`, общий на процесс реестр резерваций); резервация освобождается после записи в send-log, на ABORT и по TTL 10 минут (у go-milter нет колбэка на закрытие соединения, а вечная резервация — это fail-closed-дрейф, которого у лимитера быть не должно). Транзакция «count+insert», как предлагал review, невозможна буквально: эти два шага разнесены по разным стадиям SMTP-транзакции. Тесты: restart/rotation-resume для tailer'а, четыре сценария резерваций для лимита. `gofmt`/`go vet` чистые; `go test ./...` — падения только известные Windows-специфичные (`internal/domain`, `TestFollowTailsAndRotates`). Не проверено на стенде (нет Docker локально) — кандидат на следующий прогон на dev-сервере.
+- **Добор по плану ревизии выполнен (2026-08-06):** (1) проект переехал на единственную площадку — GitHub (Codeberg уходит): вместе с URL, лицензионными шапками SVG/HTML и docs переехал путь Go-модуля на `github.com/mixeme/selfpost` (`go.mod`, `test/e2e/go.mod`, все импорты, `MODULE` в Makefile, `-ldflags` в Dockerfile и development.md) — оставлять импорты на исчезающем хосте нельзя, `go get`/`go install` сломались бы; (2) ссылки на архивную спецификацию убраны из кода целиком — не только «spec 7.x», как просило ревью, но и «spec 4/5/6/8/9», страдавшие тем же, каждая заменена на живой документ с секцией там, где документ большой; (3) [architecture.md](architecture.md) § Code layers — диаграмма слоёв (A2); (4) `TestParseDelivery` расширен экзотикой mail.log — и **вскрыл реальный баг**: шаблон брал `status=` жадно, то есть последнее вхождение в строке, а Postfix дописывает ответ удалённого сервера дословно, поэтому отказ с `status=sent` в тексте ответа попадал в журнал как доставленный (исправлено на ленивый разбор); (5) `CONTRIBUTING.md` перенесён в 2.x, бамп тега образа и git-тег оставлены в [roadmap.md](roadmap.md) § v1.x. `gofmt`/`go vet` чистые в обоих модулях, `go test ./...` — падения только известные Windows-специфичные (`internal/domain`, `TestFollowTailsAndRotates`). На стенде не проверялось (нет Docker локально).
+- **Дальше:** релизный гейт (Фаза 0) закрыт по существу — e2e C.4 и ревизия § D пройдены; остаются только шаги, которые делаются в момент резки версии (бамп тега образа в compose, git tag) по явной команде пользователя. Все polish-фазы плана ревизии (1, 1.5, 2, 3) закрыты.
- **Принятые риски** — [security.md](security.md). **Опционально v1.x / 2.x** — [roadmap.md](roadmap.md) (хвост документации, send-log gaps, Фаза O1+, роль администратора домена).
- **Прод:** `selfpost.mixfed.ru`, реальный Let's Encrypt сертификат, живой e2e (DKIM/SPF pass). Контейнер там всё ещё на образе v1.0 — Фаза 14 в него не выкатывалась. При апгрейде: админа один раз разлогинит (сменилось имя cookie), а от reverse-proxy требуется передача исходного `Host` (Apache-фрагмент из `deploy/` это делает).
diff --git a/docs/roadmap.md b/docs/roadmap.md
index 845cc09..6c346c5 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -25,9 +25,9 @@
[deploy/docker-compose.yml](../deploy/docker-compose.yml) поле `image:` бампить
до версии релиза **в том же коммите**, что и git-тег `vX.Y.Z` — не раньше.
Сейчас там `0.1.0`, то есть отстаёт от целевой версии; несовпадение мешает
-только до первого выката по тегу. Сам тег — последний шаг Фазы 0
-[code-review.md](code-review.md): содержательная часть гейта (e2e C.4, ревизия
-§ D) закрыта, режется по явной команде оператора. После тега `release.yml`
+только до первого выката по тегу. Сам тег — последний шаг релизного гейта:
+содержательная часть (e2e C.4, ревизия § D) закрыта, режется по явной команде
+оператора ([progress.md](progress.md)). После тега `release.yml`
собирает и публикует `ghcr.io/mixeme/selfpost:X.Y.Z`, поэтому compose с новым
тегом и сам тег обязаны появиться вместе — иначе compose неделю ссылается на
несуществующий образ.
@@ -48,9 +48,9 @@ CHANGELOG `[Unreleased]/Security`, а разделы B.1–B.3 и C.4 вырез
[development.md](development.md).
3. Перецелить ссылки из документации: [README.md](../README.md) («Open v1.x
questions» — открытых вопросов там нет) → [progress.md](progress.md);
- [code-review.md](code-review.md), [security.md](security.md),
- [documentation-plan.md](documentation-plan.md), [progress.md](progress.md) и
- шапку этого файла → на `progress.md`/`security.md`.
+ [security.md](security.md), [documentation-plan.md](documentation-plan.md),
+ [progress.md](progress.md) и шапку этого файла → на
+ `progress.md`/`security.md`.
4. В [progress.md](progress.md) убрать шаг «Открыть `implementation-plan.md`» —
он выполнен.
@@ -64,8 +64,23 @@ git-тег `vX.Y.Z`; `implementation-plan.md` в `docs/archive/`, ссылок
`raw.githubusercontent.com` — это и есть единственная площадка проекта, зеркал
больше нет.)
+**Сводный индекс документации в README.** Ссылки на `docs/` разбросаны по
+тексту README (блок в шапке плюс упоминания по месту), единого списка нет —
+читателю, который ищет «а где вообще что», приходится вычитывать документ.
+Стоит одного абзаца со списком всех файлов `docs/` и одной строкой на каждый.
+Мелочь, но именно она делает набор документов набором, а не россыпью.
+
+**Опрос мониторинга у открытой, но незанятой вкладки.** Скрытая вкладка уже не
+опрашивает сервер (фильтр на `htmx:beforeRequest` в
+[panel.js](../internal/web/static/panel.js)). Остаток: вкладка на переднем
+плане, с которой не работают, всё равно ходит раз в 5 с. Кандидат — адаптивный
+интервал (5 с при активности, 30 с при простое) по `htmx:afterRequest` без
+изменения `hx-trigger`. Ценность низкая: нагрузка — один SQL-запрос и рендер
+фрагмента, так что это скорее гигиена, чем экономия. Допустимый исход —
+осознанно не делать.
+
**Send-log vs `mail.log` (частично закрыто).** Persist позиции чтения сделан
-(Фаза 3 [code-review.md](code-review.md), таблица `logtail_state`): после
+(таблица `logtail_state`, миграция `0003`): после
рестарта панели log-tailer дочитывает пропущенный хвост. Остаётся пересоздание
контейнера — `mail.log` не в `/data` и теряется вместе с ним, такие строки
навсегда останутся `queued`. Кандидаты, если станет больно: volume для лога,
@@ -139,15 +154,37 @@ Windows → сборка и прогон на Debian-сервере, потом
[development.md](development.md) и [progress.md](progress.md) — то есть на
русском и вперемешку с внутренним состоянием проекта.
-**Почему 2.x, а не v1.x** (перенесено из [code-review.md](code-review.md) § 10,
-пункт R7, где стояло как «опционально v1.x»). Файл имеет смысл, когда есть
-кому его читать: у проекта один разработчик и внешнего потока PR нет, поэтому
-сейчас `CONTRIBUTING.md` был бы документом без аудитории и ещё одним местом,
-где расходится правда о dev loop. Уместен вместе с тем, что реально открывает
-проект вовне: английская документация процесса (сейчас RU/EN split — барьер
-для EN-only контрибьюторов, [code-review.md](code-review.md) § 4) и первый
-внешний интерес после публикации релиза.
+**Почему 2.x, а не v1.x.** Файл имеет смысл, когда есть кому его читать: у
+проекта один разработчик и внешнего потока PR нет, поэтому сейчас
+`CONTRIBUTING.md` был бы документом без аудитории и ещё одним местом, где
+расходится правда о dev loop. Уместен вместе с тем, что реально открывает
+проект вовне: английская документация процесса (сейчас процессные документы —
+`progress.md`, `roadmap.md`, `development.md` — на русском, а README и
+`architecture.md` на английском; для EN-only контрибьютора это барьер) и
+первый внешний интерес после публикации релиза.
**Готово, когда:** `CONTRIBUTING.md` в корне описывает dev loop, требования к
проверкам перед PR и протокол коммитов; [development.md](development.md) не
дублирует его, а ссылается.
+
+---
+
+## Разбиение `internal/web` на подпакеты — кандидат на 2.x
+
+**Что это.** `internal/web` — самый крупный пакет проекта: 47 файлов, ~3030
+строк, в одной плоскости лежат хендлеры всех разделов панели, сессии,
+security-заголовки, проверка Origin, валидация форм и рендер шаблонов.
+Кандидаты на выделение — `web/handlers` и `web/auth`, либо разрез по доменам
+панели.
+
+**Почему 2.x, а не сейчас.** На нынешнем размере плоский пакет читается: имена
+файлов (`handlers_domains.go`, `handlers_apps.go`, `handlers_monitor.go`)
+работают не хуже каталогов, а разбиение потянуло бы за собой экспорт того, что
+сейчас пакетно-приватно, — то есть расширение внутреннего API ради
+косметики. Смысл появляется ровно тогда, когда пакет начнёт расти: обе задачи
+2.x выше добавляют в него код — роль администратора домена приносит
+авторизацию в каждый хендлер, входящий релей — отдельные страницы и хендлеры
+входящих доменов. Рефакторинг дешевле делать перед этим ростом, чем после.
+
+**Готово, когда:** решение принято осознанно в момент старта 2.x — либо пакет
+разрезан, либо зафиксировано, что он остаётся плоским.
|