docs: plan specification retirement via D9 migration map

After the documentation pass, specification.md moves to archive once its content lives in product, architecture, development, and security docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-05 00:24:13 +03:00
parent 2640ef4fd6
commit 865cf67966
2 changed files with 105 additions and 50 deletions
+6
View File
@@ -5,6 +5,12 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
## [Unreleased] ## [Unreleased]
### Changed
- docs: documentation plan now targets retiring `specification.md` after D9 —
migration map to `product.md`, `architecture.md`, `development.md`, and
expanded `security.md`; D9 added to the release gate.
## [0.4.0] - 2026-08-04 ## [0.4.0] - 2026-08-04
### Added ### Added
+99 -50
View File
@@ -1,11 +1,22 @@
# План документации SelfPost # План документации SelfPost
**Зачем этот файл.** Документация — часть поставки (ТЗ раздел 11, пп. 5, 7, 9), **Зачем этот файл.** Документация — часть поставки (deliverables v1.0, пп. 5, 7, 9
а не сопроводительный текст. Перед тегом релиза она обязана описывать **то, что в [specification.md](specification.md)), а не сопроводительный текст. Перед тегом
делает код**, а не то, что задумывалось: расхождение здесь — такой же дефект, релиза она обязана описывать **то, что делает код**, а не то, что задумывалось:
как несоответствие ТЗ, только обнаруживает его пользователь на своём проде. расхождение здесь — такой же дефект, как несоответствие требованиям, только
План описывает, из чего состоит пакет, как он сверяется с кодом, что уже обнаруживает его пользователь на своём проде. План описывает, из чего состоит
разошлось (первый проход выполнен, результаты ниже) и что с этим делать. пакет, как он сверяется с кодом, что уже разошлось (первый проход выполнен,
результаты ниже) и что с этим делать.
**Цель после выполнения плана:** [specification.md](specification.md) (ТЗ v1.0)
**выводится из обращения** — не потому что требования исчезли, а потому что v1.0
реализован и каждый блок ТЗ получает постоянный дом: пользовательское — в
README, устройство — в `architecture.md`, продуктовые границы — в
`product.md`, обязательная безопасность — в `security.md`, процесс разработки —
в `development.md`. Живой `specification.md` после этого держит два риска:
дублирование с кодом и ложное ощущение «источника истины», который уже не
сверяют. Задача **D9** — миграция остатков и снятие файла; до её закрытия
ссылки на ТЗ в этом файле — исторические.
**Место в общем плане:** документационный проход идёт вместе с **D.5** **Место в общем плане:** документационный проход идёт вместе с **D.5**
([implementation-plan.md](implementation-plan.md)) — до тега следующего релиза, ([implementation-plan.md](implementation-plan.md)) — до тега следующего релиза,
@@ -32,38 +43,49 @@
### Рабочие документы проекта (не поставка, но обязаны быть верны) ### Рабочие документы проекта (не поставка, но обязаны быть верны)
[specification.md](specification.md) — ТЗ v1.0, источник истины по требованиям. [specification.md](specification.md) — ТЗ v1.0; **на вывод после D9** (см. карту
миграции ниже). До закрытия D9 — ещё используется как историческая ссылка в
находках раздела 3.
[implementation-plan.md](implementation-plan.md) — открытые вопросы для v1.0/v1.x. [implementation-plan.md](implementation-plan.md) — открытые вопросы для v1.0/v1.x.
[roadmap.md](roadmap.md) — линия 2.x.x. [roadmap.md](roadmap.md) — линия 2.x.x.
[progress.md](progress.md) — живой трекер, читается первым после `/clear`. [progress.md](progress.md) — живой трекер, читается первым после `/clear`.
[security.md](security.md) — принятые риски. Этот файл — план по документации. [security.md](security.md) — обязательные требования безопасности + принятые
риски. Этот файл — план по документации.
**Новое: `docs/architecture.md` и `docs/development.md` нужны, решение **Новые `product.md`, `architecture.md`, `development.md` — решение принято.**
принято.** ТЗ их не требует (ТЗ 11 не относит их к обязательной поставке), но В обязательную поставку пользователю (README) они не входят, но без них
без них понимание устройства и повторная сборка проекта держатся только на понимание проекта держится на памяти сессии. Их содержимое **заменяет** живой
памяти и контексте текущей сессии — то есть не переживают её потерю ни для `specification.md` для всех будущих проходов — задачи **D8** и **D9**.
будущего контрибьютора, ни для будущего прохода без доступа к истории чата.
Обе — рабочие документы проекта (не часть поставки пользователю, не
упоминаются в README как обязательные), задача заведена в разделе 4 (**D8**).
- `docs/architecture.md` — компоненты контейнера (`supervisord`, - `docs/product.md` — из ТЗ §1–3 и §4.1: зачем SelfPost, инфраструктурные
`postfix`, `opendkim`, `panel`, milter-цепочка) и как они связаны; путь предпосылки, out of scope, мультидоменная модель «домен ↔ приложения ↔
письма через тракт (SMTP submission → milter'ы → OpenDKIM → доставка); режим From». Границы продукта, меняющиеся только явным решением.
что читает/пишет `/data` и в каком формате; где проходит граница - `docs/architecture.md` — as-built по коду: `supervisord`, `postfix`,
привилегий (root vs непривилегированный uid панели) и почему — `opendkim`, panel (HTTP + journal-milter + log-tailer); порядок старта;
источник истины: код (`build/supervisord.conf`, `internal/`), а не milter-цепочка; Postfix/SASL/TLS; журнал отправки и L1/L2 rate-limit;
ТЗ, план сверки — тот же метод из раздела 2. бэкап/restore; персистентность `/data`. Источник истины — код, не ТЗ.
- `docs/development.md`как собрать и прогнать тесты локально (Go - `docs/development.md`Go локально, `go test`/`go vet`, `make e2e`, когда
локально, без Docker — см. память проекта), когда нужен полный нужен полный контейнер на dev-сервере, ручная проверка на
контейнерный цикл на dev-сервере (сборка образа, milter/почтовые `selfpost.example.com`, протокол коммитов/CHANGELOG, правила для агента
изменения) и почему локальной сборки недостаточно, порядок ручной (бывшее ТЗ §12). В `progress.md` — текущее состояние, не процедура.
проверки на `selfpost.example.com` перед мёржем. Заменяет часть, что
раньше держалась только в памяти проекта и `progress.md` — там ### Карта миграции из `specification.md`
остаётся текущее состояние и следующий шаг, а не воспроизводимая
процедура. | Блок ТЗ | Новый дом | Задача |
|---|---|---|
| §13, §4.1 | `product.md` | D9 |
| §4, §5–7 (техника), §9 | `architecture.md` | D8 |
| §7.6 | `security.md` («Обязательные требования») | D9 |
| §8 | README (таблица env) | D2 |
| §9 (путь `tar` для пользователя) | README | D3 |
| §1011 | README + таблица «Поставка» выше | D1–D4 |
| §12 | `development.md` | D8 |
После D9: `specification.md``docs/archive/specification-v1.0.md` (снимок,
не правится); в `docs/` живых ссылок на него нет.
**Границы (без изменений):** отдельные `CONTRIBUTING.md`, man-страницы и сайт **Границы (без изменений):** отдельные `CONTRIBUTING.md`, man-страницы и сайт
документации ТЗ по-прежнему не требует и в объём v1.x не входят. документации в объём v1.x не входят.
--- ---
@@ -82,11 +104,16 @@
| Сессии, вход, смена пароля | [internal/store/sessions.go](../internal/store/sessions.go), [internal/web/session.go](../internal/web/session.go), [internal/web/handlers_account.go](../internal/web/handlers_account.go) | | Сессии, вход, смена пароля | [internal/store/sessions.go](../internal/store/sessions.go), [internal/web/session.go](../internal/web/session.go), [internal/web/handlers_account.go](../internal/web/handlers_account.go) |
| Ротация лога, периодический reload, интервалы | [build/logrotate-mail.conf](../build/logrotate-mail.conf), [build/logrotate-loop.sh](../build/logrotate-loop.sh), [build/postfix-cert-reload.sh](../build/postfix-cert-reload.sh) | | Ротация лога, периодический reload, интервалы | [build/logrotate-mail.conf](../build/logrotate-mail.conf), [build/logrotate-loop.sh](../build/logrotate-loop.sh), [build/postfix-cert-reload.sh](../build/postfix-cert-reload.sh) |
| Деплой: тег образа, порты, монтирования, capabilities | [deploy/docker-compose.yml](../deploy/docker-compose.yml), [build/Dockerfile](../build/Dockerfile) | | Деплой: тег образа, порты, монтирования, capabilities | [deploy/docker-compose.yml](../deploy/docker-compose.yml), [build/Dockerfile](../build/Dockerfile) |
| Требования, а не реализация (что вообще должно быть описано) | [specification.md](specification.md) разделы 9, 10, 11 | | Обязательное содержание README (чеклист поставки) | таблица «Поставка» в §1 этого файла (бывшее ТЗ §10–11) |
| Границы продукта, out of scope | [product.md](product.md) *(после D9)* |
| As-built устройство (не пользовательский текст) | [architecture.md](architecture.md) *(после D8)* |
| Обязательные требования безопасности | [security.md](security.md) *(после D9)* |
Порядок прохода: сначала перечислить фактическое (env-ключи, маршруты, Порядок прохода: сначала перечислить фактическое (env-ключи, маршруты,
`postconf`-настройки), потом искать каждый пункт в README — так находится и `postconf`-настройки), потом искать каждый пункт в README / `architecture.md`
неверное, и **отсутствующее**. так находится и неверное, и **отсутствующее**. До закрытия D9 для чеклиста
README допустима сверка с `specification.md` §10–11; после D9 — только с §1
этого файла.
--- ---
@@ -230,7 +257,8 @@ DNS-карточка домена); прогрев IP; смысл фиксиро
«на живом контейнере так делать не надо, потому что SQLite в WAL». Заодно «на живом контейнере так делать не надо, потому что SQLite в WAL». Заодно
упомянуть, что `manifest.json` после успешного восстановления потребляется. упомянуть, что `manifest.json` после успешного восстановления потребляется.
**D4. Точечные правки.** Находки 5, 7, 8, 11: баннер статуса и ссылки; комментарий **D4. Точечные правки.** Находки 5, 7, 8, 11: баннер статуса и ссылки (убрать
ссылку на `specification.md` — после D9 продукт в `product.md`); комментарий
в шапке compose про `.env`; строка про 587; бамп тега образа (делается в в шапке compose про `.env`; строка про 587; бамп тега образа (делается в
релизном коммите, не раньше). релизном коммите, не раньше).
@@ -245,17 +273,36 @@ DNS-карточка домена); прогрев IP; смысл фиксиро
**D7. Регресс-защита (см. раздел 5).** **D7. Регресс-защита (см. раздел 5).**
**D8. Новые документы: `docs/architecture.md` и `docs/development.md`.** **D8. Новые документы: `architecture.md` и `development.md`.** Пишутся с нуля и
Пишутся с нуля (в разделе 3 их нет — это не расхождение с кодом, а перенимают техническую часть ТЗ (§4–7, §9, §12) в форму as-built / процесса.
отсутствовавший артефакт). `architecture.md` сверяется тем же методом, что и `architecture.md` сверяется методом из раздела 2 — по коду, не по памяти.
остальное (раздел 2): по факту компоновки образа и кода, не по памяти. `development.md` фиксирует воспроизводимый процесс (Go локально / e2e /
`development.md` фиксирует уже существующий, но нигде не описанный процесс dev-сервер).
(Go локально / полный цикл на dev-сервере для Docker-зависимых изменений).
*Готово, когда:* `architecture.md` перечисляет все managed-процессы из *Готово, когда:* `architecture.md` перечисляет все managed-процессы из
[build/supervisord.conf](../build/supervisord.conf) и путь письма через [build/supervisord.conf](../build/supervisord.conf) и путь письма через
milter-цепочку без противоречий коду; `development.md` позволяет с нуля milter-цепочку без противоречий коду; `development.md` позволяет с нуля
повторить локальную сборку + прогон тестов и понять, когда нужен повторить локальную сборку + прогон тестов и понять, когда нужен dev-сервер,
dev-сервер, не заглядывая в память проекта. не заглядывая в память проекта и не открывая `specification.md`.
**D9. Вывод `specification.md` из обращения (после D1–D8).** Закрывает цель
плана: живой ТЗ больше не нужен.
1. Создать `docs/product.md` — §1–3, §4.1 (см. карту миграции в §1).
2. Дополнить [security.md](security.md) разделом «Обязательные требования» —
самодостаточный чеклист из бывшего §7.6 (setup-link, SASL, сессии,
rate-limit логина, `html/template`, не-root и т.д.), без отсылки «см. ТЗ».
3. Убедиться, что D1–D4 и D8 покрыли всё из §8–11, что должно жить в README /
`architecture.md` (пройти карту миграции построчно).
4. Перенести `specification.md``docs/archive/specification-v1.0.md` без
правок текста; в начале архива — одна строка: «исторический снимок v1.0,
не источник истины».
5. Обновить ссылки во всём репозитории: `progress.md` (убрать «ТЗ:
specification.md», заменить на `product.md` + `architecture.md`),
`implementation-plan.md` и `roadmap.md` («Основа» → `product.md`),
`security.md`, `README.md` (баннер D4 — не на ТЗ). `rg specification\.md`
по репо — только архив и CHANGELOG/история.
*Готово, когда:* в `docs/` нет `specification.md`; новый агент после `/clear`
может понять продукт, устройство, безопасность и процесс разработки, не
открывая архив.
--- ---
@@ -270,16 +317,18 @@ dev-сервер, не заглядывая в память проекта.
новом недокументированном ключе. Ловит самый частый класс расхождений новом недокументированном ключе. Ловит самый частый класс расхождений
(находка 3) без ручного прохода. Границу «внутренних» ключей задать явным (находка 3) без ручного прохода. Границу «внутренних» ключей задать явным
списком-исключением в самом тесте. списком-исключением в самом тесте.
3. **Перед каждым тегом** — короткий проход по разделу 2 этого файла (семь 3. **Перед каждым тегом** — короткий проход по разделу 2 (источники истины в
источников истины), а не полная ревизия текста. коде + README + `architecture.md` + `product.md`), а не полная ревизия текста
и не сверка с архивным ТЗ.
--- ---
## 6. Гейт релиза ## 6. Гейт релиза
Документационный проход — часть того же гейта, что e2e (C.4) и ревизия Документационный проход — часть того же гейта, что e2e (C.4) и ревизия
безопасности (D.5): **D1–D6 закрыты до тега**. D7 и D8 — желательны, но тег не безопасности (D.5): **D1D6 и D9 закрыты до тега**. D7 желателен, но тег не
блокируют: D8 не покрывает пробел против ТЗ (архитектура/разработка не входят блокирует. **D8** — обязателен до D9 (архитектура должна существовать до
в обязательную поставку), поэтому может тянуться в фон после релиза. Находки, миграции); D8 один тег не блокирует, если D9 отложен, но **полное закрытие
обнаруженные позже, дописываются сюда, а не исправляются молча: этот файл — плана (= вывод specification) — только D1–D9 вместе**. Находки, обнаруженные
журнал состояния документации, а не одноразовый список дел. позже, дописываются сюда, а не исправляются молча: этот файл — журнал
состояния документации, а не одноразовый список дел.