2f30928267
The unit tests all fake the process boundary, so the class of failure that actually broke this project — container wiring: chroot vs DNS, milter socket permissions, the SASL realm, the Postfix reload path, cap_add — is invisible to them. Record the decision to close it with a hermetic containerised e2e: a separate Go module under test/e2e/, driven against the shipped compose file plus an override, with a fake DNS zone and an smtp-sink standing in for the outside world. It runs before tagging (make e2e on the dev server, plus workflow_dispatch) and gates image publication on the tag itself, which pulls release.yml off qemu onto a native amd64/arm64 matrix: build, test, push per-arch tags, merge the manifest — so the bytes published are the bytes that were tested. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
45 lines
7.4 KiB
Markdown
45 lines
7.4 KiB
Markdown
# Прогресс реализации SelfPost
|
||
|
||
Живой трекер состояния. **Переживает `/clear`** — читается первым при возобновлении работы.
|
||
План (открытые вопросы + опциональные фазы): [implementation-plan.md](implementation-plan.md).
|
||
ТЗ: [specification.md](specification.md). История релизов: [CHANGELOG.md](../CHANGELOG.md).
|
||
История сделанного по фазам (0→13, все закрыты) — в `git log` и в CHANGELOG, здесь не дублируется.
|
||
|
||
## Как возобновить после сброса контекста
|
||
|
||
1. Прочитать этот файл (текущее состояние, что дальше).
|
||
2. Открыть `implementation-plan.md` — там нерешённые вопросы, принятые риски и опциональная линия 2.x.x (Фаза O1+).
|
||
3. При необходимости — детали в `specification.md`.
|
||
4. Продолжить с пункта «Следующий шаг».
|
||
|
||
## Модель по типу работы
|
||
|
||
Правило: безопасность / инфра / риск-критичное → **Opus**; UI / документация / бойлерплейт → **Sonnet**; тривиальная механика → **Haiku**.
|
||
|
||
## Коммиты
|
||
|
||
Коммит на **каждом осмысленном шаге** (не каждое сохранение файла, но и не только конец фазы): рабочий под-функционал, зелёная сборка, конец фазы. Минимум — один коммит на закрытую фазу + промежуточные на связные под-шаги. Ветка `main` (если пользователь не попросит отдельную). Push/PR — только по явной команде. Сообщение коммита завершается трейлером `Co-Authored-By: Claude <модель> <noreply@anthropic.com>` — с той моделью, которая этот шаг делала (на момент Фазы 14 — `Claude Opus 5`).
|
||
|
||
На каждом таком шаге — запись в [CHANGELOG.md](../CHANGELOG.md) под `[Unreleased]` (формат Keep a Changelog). При явном решении зарезать версию — секция `[Unreleased]` переименовывается в `[X.Y.Z] - дата`, заводится новая пустая `[Unreleased]`. Тег/пуш образа — только по явному запросу (см. workflow release.yml).
|
||
|
||
## Протокол закрытия фазы/крупного шага
|
||
|
||
Перед `/clear` в конце каждого законченного шага Claude:
|
||
1. Обновляет этот файл: «Текущее состояние» → что изменилось, что дальше.
|
||
2. Проверяет применимые критерии «Готово, когда…».
|
||
3. Дописывает `CHANGELOG.md` под `[Unreleased]`.
|
||
4. Делает финальный коммит шага.
|
||
|
||
---
|
||
|
||
## Текущее состояние
|
||
|
||
- **Выполнено и принято:** базовый линейный план 0→11 (v1.0; аудит безопасности ТЗ 7.6 — полное соответствие), Фаза 12 (UI/UX), Фаза 13 (страница `/status`, DNS-проверки домена) и Фаза 14 (security-заголовки, проверка origin, cookie `__Host-` + обнаружение дублей, документация про `/data/setup-token`). Что именно сделано — в `git log` и `CHANGELOG.md`, здесь не дублируется.
|
||
- **Решено, но ещё не реализовано:** пункты **B.1**, **B.2**, **B.3** и **C.4** плана, именно в этом порядке. B.1 — сессии переезжают в SQLite (скользящий срок бездействия 7 дней, `PANEL_SESSION_IDLE_DAYS`, опросы не продлевают, смена пароля разлогинивает всех). B.2 — ротация `mail.log` уходит с `copytruncate` на «переименовать + `postfix reload`» (правки в `logrotate-mail.conf`, `follow()` в `internal/logtail`, `readLogTail()` в `internal/web`; на стенде проверить, что после reload новый `mail.log` создаётся). B.3 — незаданный `SELFPOST_HOSTNAME` роняет контейнер в `entrypoint.sh` с развёрнутым текстом ошибки плюс синтаксическая проверка значения. C.4 — герметичный контейнерный e2e отдельным Go-модулем `test/e2e/` поверх поставляемого compose, гейт перед публикацией образа по тегу, нативная матрица amd64/arm64 вместо qemu в `release.yml`; делается **после** B.1–B.3, стендовые проверки B.1/B.3 переезжают в него регрессиями. Параметры всех четырёх согласованы полностью, кода нет: делать отдельными шагами.
|
||
- **Дальше — то, что перечислено в `implementation-plan.md`:** из открытых вопросов не решён только раздел D (указатель на объём 2.x); плюс принятые риски раздела A (`POST` без `Sec-Fetch-Site`/`Origin` пропускается, токенов нет) и опциональная **Фаза O1+** (входящий релей, линия 2.x.x, требует согласования).
|
||
- **Прод:** `selfpost.mixfed.ru`, реальный Let's Encrypt сертификат, живой e2e (DKIM/SPF pass). Контейнер там всё ещё на образе v1.0 — Фаза 14 в него не выкатывалась. При апгрейде: админа один раз разлогинит (сменилось имя cookie), а от reverse-proxy требуется передача исходного `Host` (Apache-фрагмент из `deploy/` это делает).
|
||
|
||
## Рабочая петля (dev loop) — ВАЖНО
|
||
|
||
Локально (Windows, `D:\Local\Git\selfpost`) **нет Go и Docker** — только редактирование и git. Вся сборка/тесты идут на dev-сервере `selfpost.mixfed.ru` (Debian 12 bookworm, тот же, что базовый образ; провижён под разработку). Цикл: править локально → залить дерево на сервер → `go build`/`go vet`/`docker build`/тесты там. `rsync` в локальном git-bash нет, поэтому дерево едет tar'ом по ssh: `tar -czf - --exclude=.git . | ssh root@selfpost.mixfed.ru 'rm -rf /root/selfpost-src && mkdir -p /root/selfpost-src && tar -xzf - -C /root/selfpost-src'`; Go на сервере — в `/usr/local/go/bin` (не в `PATH` по умолчанию); образ — `docker build -f build/Dockerfile -t selfpost:dev --build-arg VERSION=dev .`. Источник истины и git-история — локальный репозиторий; сервер — только исполнитель сборки/тестов. Подключение: `ssh root@selfpost.mixfed.ru` (по ключу).
|