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>
7.4 KiB
Прогресс реализации SelfPost
Живой трекер состояния. Переживает /clear — читается первым при возобновлении работы.
План (открытые вопросы + опциональные фазы): implementation-plan.md.
ТЗ: specification.md. История релизов: CHANGELOG.md.
История сделанного по фазам (0→13, все закрыты) — в git log и в CHANGELOG, здесь не дублируется.
Как возобновить после сброса контекста
- Прочитать этот файл (текущее состояние, что дальше).
- Открыть
implementation-plan.md— там нерешённые вопросы, принятые риски и опциональная линия 2.x.x (Фаза O1+). - При необходимости — детали в
specification.md. - Продолжить с пункта «Следующий шаг».
Модель по типу работы
Правило: безопасность / инфра / риск-критичное → Opus; UI / документация / бойлерплейт → Sonnet; тривиальная механика → Haiku.
Коммиты
Коммит на каждом осмысленном шаге (не каждое сохранение файла, но и не только конец фазы): рабочий под-функционал, зелёная сборка, конец фазы. Минимум — один коммит на закрытую фазу + промежуточные на связные под-шаги. Ветка main (если пользователь не попросит отдельную). Push/PR — только по явной команде. Сообщение коммита завершается трейлером Co-Authored-By: Claude <модель> <noreply@anthropic.com> — с той моделью, которая этот шаг делала (на момент Фазы 14 — Claude Opus 5).
На каждом таком шаге — запись в CHANGELOG.md под [Unreleased] (формат Keep a Changelog). При явном решении зарезать версию — секция [Unreleased] переименовывается в [X.Y.Z] - дата, заводится новая пустая [Unreleased]. Тег/пуш образа — только по явному запросу (см. workflow release.yml).
Протокол закрытия фазы/крупного шага
Перед /clear в конце каждого законченного шага Claude:
- Обновляет этот файл: «Текущее состояние» → что изменилось, что дальше.
- Проверяет применимые критерии «Готово, когда…».
- Дописывает
CHANGELOG.mdпод[Unreleased]. - Делает финальный коммит шага.
Текущее состояние
- Выполнено и принято: базовый линейный план 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.example.com, реальный Let's Encrypt сертификат, живой e2e (DKIM/SPF pass). Контейнер там всё ещё на образе v1.0 — Фаза 14 в него не выкатывалась. При апгрейде: админа один раз разлогинит (сменилось имя cookie), а от reverse-proxy требуется передача исходногоHost(Apache-фрагмент изdeploy/это делает).
Рабочая петля (dev loop) — ВАЖНО
Локально (Windows, <repo>) нет Go и Docker — только редактирование и git. Вся сборка/тесты идут на dev-сервере selfpost.example.com (Debian 12 bookworm, тот же, что базовый образ; провижён под разработку). Цикл: править локально → залить дерево на сервер → go build/go vet/docker build/тесты там. rsync в локальном git-bash нет, поэтому дерево едет tar'ом по ssh: tar -czf - --exclude=.git . | ssh root@selfpost.example.com '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.example.com (по ключу).