release: cut 1.0.0
test / test (push) Has been cancelled
release / prepare (push) Has been cancelled
release / build (amd64, ubuntu-latest) (push) Has been cancelled
release / build (arm64, ubuntu-24.04-arm) (push) Has been cancelled
release / merge (push) Has been cancelled

Pin compose and local trial to ghcr.io/mixeme/selfpost:1.0.0, close the
CHANGELOG cut, retire implementation-plan and v1.x-closure-plan, and point
e2e/CI comments at development.md.

Co-Authored-By: Composer <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
mixeme
2026-08-09 00:52:49 +03:00
parent e0b754bafe
commit fa9d2016cc
12 changed files with 58 additions and 363 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ name: release
# can never drift apart (the invariant restore's version check in spec 7.5.A # can never drift apart (the invariant restore's version check in spec 7.5.A
# depends on). # depends on).
# #
# Native per-architecture builds (plan implementation-plan.md C.4), not qemu: # Native per-architecture builds (see docs/development.md), not qemu:
# running the full Postfix/OpenDKIM stack under emulation for the e2e gate # running the full Postfix/OpenDKIM stack under emulation for the e2e gate
# below is impractically slow. Each arch builds, e2e-gates and pushes its own # below is impractically slow. Each arch builds, e2e-gates and pushes its own
# tag on its own native runner; a merge job then combines them into the one # tag on its own native runner; a merge job then combines them into the one
@@ -68,7 +68,7 @@ jobs:
go-version: "1.26" go-version: "1.26"
cache-dependency-path: test/e2e/go.sum cache-dependency-path: test/e2e/go.sum
- name: e2e (gates publishing — see docs/implementation-plan.md C.4) - name: e2e (gates publishing — see docs/development.md)
run: cd test/e2e && go test -v -timeout 20m ./... run: cd test/e2e && go test -v -timeout 20m ./...
- name: Log in to ghcr.io - name: Log in to ghcr.io
+11 -4
View File
@@ -5,6 +5,8 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
## [Unreleased] ## [Unreleased]
## [1.0.0] - 2026-08-09
### Added ### Added
- A **Delivery log** on each delivery's page (`/deliveries/{id}`): the - A **Delivery log** on each delivery's page (`/deliveries/{id}`): the
@@ -61,10 +63,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
layering and route table match `web``store` and `POST /domains/import`; layering and route table match `web``store` and `POST /domains/import`;
OpenDKIM drops to `opendkim` via `UserID`; guide drops the archived OpenDKIM drops to `opendkim` via `UserID`; guide drops the archived
"spec 7.5" pointer, clarifies `POSTFIX_SENDER_LOGIN_MAPS` vs panel writes, "spec 7.5" pointer, clarifies `POSTFIX_SENDER_LOGIN_MAPS` vs panel writes,
and states logrotate keeps 14 daily files. Intermediate CHANGELOG cuts vs and states logrotate keeps 14 daily files. Intermediate CHANGELOG cuts from
the still-pinned compose `0.1.0` image are called out in the guide and before the published `1.0.0` image are called out in the guide and
`development.md`. Roadmap / implementation-plan point at CHANGELOG `development.md`. Roadmap points at CHANGELOG `[0.5.0]` Security and
`[0.5.0]` Security and refreshed `internal/web` size / symbol links. refreshed `internal/web` size / symbol links.
- Full backups no longer carry `/data/log`. It is Postfix's raw log plus its - Full backups no longer carry `/data/log`. It is Postfix's raw log plus its
fourteen rotated copies — diagnostic output rather than state to restore, and fourteen rotated copies — diagnostic output rather than state to restore, and
otherwise by far the largest thing in the archive. otherwise by far the largest thing in the archive.
@@ -94,6 +96,11 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
dev-host-specific workflow and `mixfed.ru` references removed from docs. dev-host-specific workflow and `mixfed.ru` references removed from docs.
- `docs/development.md` and `.cursor/rules/agent-rules.mdc` translated to - `docs/development.md` and `.cursor/rules/agent-rules.mdc` translated to
English; `roadmap.md` remains Russian (internal tracker). English; `roadmap.md` remains Russian (internal tracker).
- Deploy pin and local-trial image tag set to `ghcr.io/mixeme/selfpost:1.0.0`
(compose and git tag `v1.0.0` cut together). Retired
`docs/implementation-plan.md` and `docs/v1.x-closure-plan.md`; Makefile,
`release.yml`, and e2e comments point at `docs/development.md`. Roadmap
v1.x documentation/deploy tail closed.
## [0.6.0] - 2026-08-08 ## [0.6.0] - 2026-08-08
+1 -1
View File
@@ -30,7 +30,7 @@ test:
clean: clean:
rm -rf bin rm -rf bin
# Hermetic container e2e (plan implementation-plan.md C.4): separate Go module # Hermetic container e2e (see docs/development.md): separate Go module
# under test/e2e so its test-only dependencies (DKIM verification) never enter # under test/e2e so its test-only dependencies (DKIM verification) never enter
# this module's build graph. Builds the image fresh from this checkout, brings # this module's build graph. Builds the image fresh from this checkout, brings
# up deploy/docker-compose.yml plus a test-only override on high ports and an # up deploy/docker-compose.yml plus a test-only override on high ports and an
+1 -1
View File
@@ -92,7 +92,7 @@ docker run --rm -d --name selfpost-try \
-e SELFPOST_HOSTNAME=mail.local.test \ -e SELFPOST_HOSTNAME=mail.local.test \
-e PANEL_COOKIE_SECURE=false \ -e PANEL_COOKIE_SECURE=false \
-v selfpost-try-data:/data \ -v selfpost-try-data:/data \
ghcr.io/mixeme/selfpost:0.1.0 ghcr.io/mixeme/selfpost:1.0.0
``` ```
**Get the setup URL** (pick one): **Get the setup URL** (pick one):
+1 -1
View File
@@ -22,7 +22,7 @@
services: services:
selfpost: selfpost:
image: ghcr.io/mixeme/selfpost:0.1.0 image: ghcr.io/mixeme/selfpost:1.0.0
restart: unless-stopped restart: unless-stopped
environment: environment:
SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}" SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}"
+12 -23
View File
@@ -1,8 +1,7 @@
# SelfPost — development # SelfPost — development
**What this file is.** How to build, test, document, and ship changes. Open **What this file is.** How to build, test, document, and ship changes. Open
work for v1.x and 2.x lives in [roadmap.md](roadmap.md) (and, until the tag, work for 2.x lives in [roadmap.md](roadmap.md). Product boundaries:
[v1.x-closure-plan.md](v1.x-closure-plan.md)). Product boundaries:
[product.md](product.md). As-built layout: [architecture.md](architecture.md). [product.md](product.md). As-built layout: [architecture.md](architecture.md).
--- ---
@@ -12,10 +11,8 @@ work for v1.x and 2.x lives in [roadmap.md](roadmap.md) (and, until the tag,
After `/clear` or a fresh chat: After `/clear` or a fresh chat:
1. Read this file (process, docs rules, model routing). 1. Read this file (process, docs rules, model routing).
2. Open [roadmap.md](roadmap.md) for open work; until `v1.0.0`, also 2. Open [roadmap.md](roadmap.md) for open work. Accepted risks —
[v1.x-closure-plan.md](v1.x-closure-plan.md) for the remaining closure [security.md](security.md); as-built — [architecture.md](architecture.md).
checklist. Accepted risks — [security.md](security.md); as-built —
[architecture.md](architecture.md).
3. Skim [product.md](product.md) if scope is in doubt. 3. Skim [product.md](product.md) if scope is in doubt.
4. Continue from the next unchecked step in the active plan. 4. Continue from the next unchecked step in the active plan.
@@ -31,7 +28,7 @@ not duplicated here.
| Security, infra, file permissions, Postfix/`postqueue`, open-relay risk | **Opus** | `mail.log` under `/data`, entrypoint permissions, queue reconcile | | Security, infra, file permissions, Postfix/`postqueue`, open-relay risk | **Opus** | `mail.log` under `/data`, entrypoint permissions, queue reconcile |
| UI / JS / CSS, templates, documentation (English), README | **Sonnet** | adaptive polling, this file's Documentation section | | UI / JS / CSS, templates, documentation (English), README | **Sonnet** | adaptive polling, this file's Documentation section |
| Trivial mechanics: retarget links, grep, compose bump, CHANGELOG cut | **Haiku** | Makefile / release.yml comment fixes, deleting closed plan files | | Trivial mechanics: retarget links, grep, compose bump, CHANGELOG cut | **Haiku** | Makefile / release.yml comment fixes, deleting closed plan files |
| Security **review** (not authorship) | **Fable** | pre-release checklist pass ([implementation-plan.md](implementation-plan.md) § D — done) | | Security **review** (not authorship) | **Fable** | pre-release checklist (CHANGELOG `[0.5.0]` Security / [security.md](security.md) — done) |
Default rule: risk-critical → Opus; UI / docs / boilerplate → Sonnet; trivial Default rule: risk-critical → Opus; UI / docs / boilerplate → Sonnet; trivial
mechanics → Haiku. Reviewers must not be the author of the code under review. mechanics → Haiku. Reviewers must not be the author of the code under review.
@@ -154,20 +151,16 @@ The release image is published **only on tag** `vX.Y.Z` (not on every push to
**Steps (on explicit request):** **Steps (on explicit request):**
1. Close `[Unreleased]` in [CHANGELOG.md](../CHANGELOG.md). 1. Close `[Unreleased]` in [CHANGELOG.md](../CHANGELOG.md) and bump the pinned
2. Create and push git tag `vX.Y.Z`. tag in [deploy/docker-compose.yml](../deploy/docker-compose.yml) (and any
local-trial image references) in the **same** release commit.
2. Create and push git tag `vX.Y.Z` on that commit.
3. Workflow [release.yml](../.github/workflows/release.yml) builds, e2e-gates, 3. Workflow [release.yml](../.github/workflows/release.yml) builds, e2e-gates,
and publishes `ghcr.io/mixeme/selfpost:X.Y.Z`. and publishes `ghcr.io/mixeme/selfpost:X.Y.Z`.
4. Update the pinned tag in
[deploy/docker-compose.yml](../deploy/docker-compose.yml) in the **same**
commit as the tag (see [roadmap.md](roadmap.md) § «v1.x — documentation and
deploy tail»).
Ordinary commits **do not** publish an image. Intermediate CHANGELOG version Ordinary commits **do not** publish an image. The compose pin and the git tag
cuts (`0.2.0``0.6.0`) document history on `main`; the compose pin and the only must match (`1.0.0` / `v1.0.0` for the first published release). Intermediate
git release tag may lag until an explicit image publish (today: compose CHANGELOG sections (`0.2.0``0.6.0`) record development history before that cut.
`0.1.0`, tag `v0.0.1` — see [roadmap.md](roadmap.md) § «v1.x — documentation
and deploy tail»).
--- ---
@@ -280,13 +273,9 @@ There is no `docs/archive/` directory.
| As-built design | [architecture.md](architecture.md) | | As-built design | [architecture.md](architecture.md) |
| Development process (this file) | [development.md](development.md) | | Development process (this file) | [development.md](development.md) |
| Security requirements and accepted risks | [security.md](security.md) | | Security requirements and accepted risks | [security.md](security.md) |
| Internal roadmap (v1.x tail, 2.x) | [roadmap.md](roadmap.md) | | Internal roadmap (2.x) | [roadmap.md](roadmap.md) |
| Release history | [CHANGELOG.md](../CHANGELOG.md) | | Release history | [CHANGELOG.md](../CHANGELOG.md) |
`implementation-plan.md` remains only until the release cut (describes the
closed release gate); delete it in the release commit. Temporary
[v1.x-closure-plan.md](v1.x-closure-plan.md) goes away with that cut too.
### User-facing deliverables ### User-facing deliverables
| Artefact | Role | | Artefact | Role |
+8 -9
View File
@@ -375,12 +375,11 @@ but it can look like an open port in external scans.
## Fixed image tag ## Fixed image tag
`deploy/docker-compose.yml` pins an explicit version (`ghcr.io/mixeme/selfpost:X.Y.Z`), `deploy/docker-compose.yml` pins an explicit version (`ghcr.io/mixeme/selfpost:X.Y.Z`),
deliberately never `:latest`. Until the `v1.0.0` cut the shipped pin is still deliberately never `:latest`. The current pin is `1.0.0`. Intermediate
`0.1.0` — intermediate CHANGELOG sections (`0.2.0`…`0.6.0`) record development CHANGELOG sections (`0.2.0`…`0.6.0`) record development cuts from before that
cuts and do not imply a published image of that tag. Pinning matters because of image was published. Pinning matters because of the backup version check above:
the backup version check above: the panel binary's embedded version and the the panel binary's embedded version and the image tag that produced it are the
image tag that produced it are the same value by construction (the release CI same value by construction (the release CI stamps both from one git tag — see
stamps both from one git tag — see `.github/workflows/release.yml`), so the `.github/workflows/release.yml`), so the pin is what makes "restore into the
pin is what makes "restore into the same version" a checkable fact rather than same version" a checkable fact rather than a guess. Upgrade by bumping the tag
a guess. Upgrade by bumping the tag deliberately, not by riding a moving deliberately, not by riding a moving target.
target.
-32
View File
@@ -1,32 +0,0 @@
# План реализации: SelfPost
**Статус:** линия v1.0/v1.x до тега релиза **закрыта** — предрелизная ревизия
безопасности (§ 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). Текущее состояние и следующий шаг:
[roadmap.md](roadmap.md) и [v1.x-closure-plan.md](v1.x-closure-plan.md).
Объём 2.x.x — [roadmap.md](roadmap.md).
**Основа:** [product.md](product.md) v1.0.
---
## D. Предрелизная ревизия безопасности — ВЫПОЛНЕНО (2026-08-06)
**Проверка моделью Fable** по дифу от аудита v1.0 (Фаза 11, `bd64e80` — тега
`v1.0.0` в репозитории нет, это его фактический эквивалент) до HEAD, плюс
полный повторный проход по чек-листу безопасности (бывшее ТЗ 7.6, теперь
[security.md](security.md)). Приоритеты из плана покрыты: аутентификация и
сессии, валидация ввода, запись в конфиги/map-файлы, `os/exec`, права в
`/data`, секреты.
**Результат.** Эксплуатируемых уязвимостей (high/medium) не найдено. Одна
находка defence-in-depth закрыта правкой до тега: логин приложения передаётся
в `saslpasswd2` после `--`, чтобы значение, начинающееся с `-` (допустимо
whitelist'ом), не могло быть разобрано getopt как флаг
([internal/app/sasl.go](../internal/app/sasl.go)). Принятые риски в
[security.md](security.md) не пополнились — существующие записи (origin-check
fallback, отсутствие CSRF-токенов, ложно-отрицательный `bounced` при сверке
с `postqueue`) покрывают всё найденное.
Сводка ревизии — в записи `Security` CHANGELOG `[0.5.0]`.
+12 -71
View File
@@ -11,84 +11,25 @@
решением. решением.
**Основа:** [product.md](product.md) v1.0. Процесс и правила документации — **Основа:** [product.md](product.md) v1.0. Процесс и правила документации —
[development.md](development.md). Несделанное для v1.0/v1.x до тега — в [development.md](development.md). История закрытых фаз v1.x — в `git log` и
[implementation-plan.md](implementation-plan.md) и [CHANGELOG.md](../CHANGELOG.md).
[v1.x-closure-plan.md](v1.x-closure-plan.md). Хвост закрытого
документационного прохода (D1–D9) — в секции ниже.
--- ---
## v1.x — хвост документации и деплоя ## v1.x — хвост документации и деплоя
**Статус:** не блокирует релизный тег; бывший хвост закрытого **Статус: закрыто** в релизе `1.0.0` / git-тег `v1.0.0`
документационного прохода (D1–D9). Делать по желанию или в релизном коммите, (`ghcr.io/mixeme/selfpost:1.0.0`). План закрытия и `implementation-plan.md`
где указано. Сводка чек-листов до тега — удалены — история в git и CHANGELOG; `docs/archive/` не храним.
[v1.x-closure-plan.md](v1.x-closure-plan.md).
**Тег образа в compose + git tag — один релизный коммит (R1).** В | Тема | Итог |
[deploy/docker-compose.yml](../deploy/docker-compose.yml) поле `image:` бампить |---|---|
до версии релиза **в том же коммите**, что и git-тег `vX.Y.Z` — не раньше. | Адаптивный опрос мониторинга | 5 с / 30 с / 0 (скрытая вкладка) в `panel.js` |
Сейчас там `0.1.0`, то есть отстаёт от целевой версии; несовпадение мешает | `mail.log` + reconcile | `/data/log/mail.log`; сверка с `postqueue -p` |
только до первого выката по тегу. Сам тег — последний шаг релизного гейта: | Docs consolidation | процесс в [development.md](development.md); README Documentation |
содержательная часть (e2e C.4, ревизия § D) закрыта, режется по явной команде | Compose pin + git tag | `1.0.0` / `v1.0.0` в одном релизном коммите |
оператора ([development.md](development.md) § Commits and release build). После
тега `release.yml` собирает и публикует `ghcr.io/mixeme/selfpost:X.Y.Z`,
поэтому compose с новым тегом и сам тег обязаны появиться вместе — иначе
compose неделю ссылается на несуществующий образ.
**Убрать `implementation-plan.md` — в релизном коммите.** Документ закрыт: Открытая работа дальше — только секции 2.x ниже.
уникального содержания в нём нет, § D (предрелизная ревизия безопасности)
продублирован в [security.md](security.md) и CHANGELOG `[0.5.0]/Security`,
а разделы B.1–B.3 и C.4 вырезаны ещё в `22f86d1`. Держится до тега только
потому, что описывает релизный гейт, пока тот формально не закрыт. При резке
версии:
1. Удалить файл (история § D — в git и CHANGELOG; `docs/archive/` не храним).
2. Перецелить ссылки из кода и CI ([Makefile](../Makefile),
[.github/workflows/release.yml](../.github/workflows/release.yml),
[test/e2e/main_test.go](../test/e2e/main_test.go)) — они ссылаются на «план
C.4», секцию, которой в файле уже нет; актуальное описание e2e — в
[development.md](development.md).
3. Перецелить оставшиеся ссылки из документации на
[development.md](development.md) / [security.md](security.md) /
[roadmap.md](roadmap.md).
4. Удалить [v1.x-closure-plan.md](v1.x-closure-plan.md) в том же или следующем
коммите.
**Готово, когда:** тег образа в compose совпадает с релизом и рядом стоит
git-тег `vX.Y.Z`; `implementation-plan.md` и `v1.x-closure-plan.md` удалены,
ссылок на них в активных документах и в коде/CI не осталось.
(Закрыто и действия не требует: `docs/logo` как каталога нет — критерию «либо
содержит файлы, либо отсутствует» удовлетворяет; Quick start в
[README.md](../README.md) тянет `docker-compose.yml` и `.env.example` с
`raw.githubusercontent.com` — это и есть единственная площадка проекта, зеркал
больше нет.)
**Сводный индекс документации в README.** ~~Ссылки на `docs/` разбросаны по
тексту README…~~ **Закрыто (v1.x-closure Фаза 3):** секция Documentation в
[README.md](../README.md) — единый список operator docs + roadmap.
**Опрос мониторинга у открытой, но незанятой вкладки.** ~~Скрытая вкладка уже не
опрашивает сервер (фильтр на `htmx:beforeRequest` в
[panel.js](../internal/web/static/panel.js)). Остаток: вкладка на переднем
плане, с которой не работают, всё равно ходит раз в 5 с. Кандидат — адаптивный
интервал (5 с при активности, 30 с при простое) по `htmx:afterRequest` без
изменения `hx-trigger`. Ценность низкая: нагрузка — один SQL-запрос и рендер
фрагмента, так что это скорее гигиена, чем экономия. Допустимый исход —
осознанно не делать.~~ **Закрыто (v1.x-closure Фаза 1):** адаптивный интервал
5 с / 30 с / 0 (скрытая вкладка) в `panel.js` через `data-poll`.
**Send-log vs `mail.log`.** ~~Persist позиции чтения сделан (таблица
`logtail_state`, миграция `0003`): после рестарта панели log-tailer дочитывает
пропущенный хвост. Остаётся пересоздание контейнера — `mail.log` не в `/data` и
теряется вместе с ним, такие строки навсегда останутся `queued`. Кандидаты, если
станет больно: volume для лога, сверка зависших строк через `postqueue`.~~
**Закрыто (v1.x-closure Фаза 2):** сделаны оба кандидата — `mail.log` переехал в
`/data/log/`, а строки, чьи delivery-строки потеряны безвозвратно, закрываются
сверкой с `postqueue -p` (grace 2 мин → `bounced`). As-built и оставшийся риск
(ложный `bounced`): [architecture.md](architecture.md) § Log tailer,
[security.md](security.md).
--- ---
+4 -4
View File
@@ -94,8 +94,8 @@ Hardening сверх обязательного (security-заголовки, п
## Принятые риски ## Принятые риски
Здесь, а не в [implementation-plan.md](implementation-plan.md): план — про Принятый риск — решение с условием возврата, а не отложенная задача из
несделанную работу, принятый риск — решение с условием возврата. дорожной карты.
- **`POST` без `Sec-Fetch-Site` и без `Origin` пропускается.** - **`POST` без `Sec-Fetch-Site` и без `Origin` пропускается.**
Клиент, не посылающий ни одного из двух — по-настоящему старый браузер или Клиент, не посылающий ни одного из двух — по-настоящему старый браузер или
@@ -171,7 +171,7 @@ Origin-проверка закрывает это без изменения ни
## Как этот список пополняется ## Как этот список пополняется
Предрелизная проверка на уязвимости ([implementation-plan.md](implementation-plan.md) Предрелизная проверка на уязвимости (модель Fable; история — CHANGELOG
§ D, модель Fable) закрывает каждую находку одним из двух способов: правка до `[0.5.0]` Security) закрывает каждую находку одним из двух способов: правка до
тега — либо запись сюда, с обоснованием и условием возврата, как у пунктов выше. тега — либо запись сюда, с обоснованием и условием возврата, как у пунктов выше.
Третьего варианта («посмотрели и ладно») нет. Третьего варианта («посмотрели и ладно») нет.
-209
View File
@@ -1,209 +0,0 @@
# План закрытия хвоста v1.x
**Статус:** в работе. **Целевой релиз:** `v1.0.0` / `ghcr.io/mixeme/selfpost:1.0.0`.
**Временный файл:** после закрытия v1.x удалить (история — git + CHANGELOG). Не часть постоянного пакета docs.
**Контекст:** релизный гейт по коду закрыт (B.1B.3, C.4 e2e, § D Fable). Остаток — [roadmap.md](roadmap.md) § «v1.x — хвост документации и деплоя» + код из того же секции.
**Политика docs:** `docs/archive/` не храним; устаревшие планы удаляем после переноса полезного в [development.md](development.md).
---
## Рекомендация по модели
Общее правило (переносится в development.md):
| Тип работы | Модель | Примеры в этом плане |
|------------|--------|---------------------|
| Безопасность, инфра, права файлов, postfix/postqueue, open-relay-риски | **Opus** | mail.log в `/data`, entrypoint permissions, postqueue reconcile |
| UI/JS/CSS, шаблоны, документация (English), README | **Sonnet** | adaptive polling, development.md § Documentation, README index |
| Тривиальная механика: retarget ссылок, grep, compose bump, CHANGELOG cut | **Haiku** | Makefile/release.yml комментарии, удаление файлов |
| Ревизия безопасности (**review**, не authorship) | **Fable** | не в этом плане (§ D уже выполнен) |
| Фаза / коммит | Модель | Почему |
|---------------|--------|--------|
| 1. Polling | Sonnet | panel.js + HTMX, CSP |
| 2. mail.log + reconcile | Opus (+ Sonnet на unit-тесты) | postfix path, logrotate, entrypoint, postqueue |
| 3. Docs consolidation | Sonnet | development.md, README, agent-rules |
| 4. Release `1.0.0` | Haiku / Sonnet | механика релиза, без новой логики |
| 5. Tag / push | **Оператор** | явная команда |
Сообщение коммита: трейлер `Co-Authored-By: Claude <модель> <noreply@anthropic.com>` с моделью, которая делала шаг.
---
## Общий чек-лист прогресса
- [x] **Фаза 1** — адаптивный опрос мониторинга
- [x] **Фаза 2** — mail.log в `/data` + postqueue reconcile
- [x] **Фаза 3** — docs: development.md, README, удаление планов и `docs/archive/`
- [ ] **Фаза 4** — релизный коммит `1.0.0` (по явной команде)
- [ ] **Фаза 5** — tag `v1.0.0` + push (по явной команде)
- [ ] **Фаза 6** — выкат на прод (оператор)
**Гейт перед тегом:**
- [ ] `gofmt -l .` чистый
- [ ] `go vet ./...` чистый
- [ ] `go test ./...` чистый (Windows-падения domain/logtail — известны, не блокер)
- [ ] `make e2e` зелёный (если Docker доступен)
- [ ] grep: нет ссылок на удалённые docs в живых файлах
---
## Фаза 1 — Адаптивный опрос мониторинга
**Модель:** Sonnet.
**Цель:** 5 s при активности, 30 s при простое; скрытая вкладка — 0 запросов. Без `hx-trigger="every 5s [expr]"` (CSP / `unsafe-eval`).
### Чек-лист
- [x] `status_body.html`, `mail_queue_body.html`, `system_log_body.html`, `deliveries_rows.html``hx-trigger="load"` + маркер polling
- [x] `panel.js``lastActivity`, schedule после `htmx:afterRequest` (5s / 30s)
- [x] `htmx:afterSwap` — реинициализация polling-элементов
- [x] `beforeRequest` для hidden tab — оставлен
- [x] `architecture.md` — одна строка про polling
- [x] CHANGELOG `[Unreleased]`
- [ ] Стенд (опционально): idle → ~30s; активность → ~5s; hidden → 0 запросов
---
## Фаза 2 — Send-log vs `mail.log`
**Модель:** Opus (infra); Sonnet (тесты).
### 2.1 `mail.log``/data/log/mail.log`
- [x] `build/postfix-config.sh``maillog_file` из `MAIL_LOG`
- [x] `build/logrotate-mail.conf` — путь + `create 0640 postfix selfpost`
- [x] `build/entrypoint.sh``mkdir`, права (`2750 postfix:selfpost`, файл `0640`),
исключение `/data/log` из общего `chown` на `panel`
- [x] `cmd/panel/main.go` — default `MAIL_LOG`
- [x] `internal/backup` — исключить `log/` из архива (+ тест)
- [x] `test/e2e/mail_helpers.go` — путь в контейнере
- [x] `guide.md``MAIL_LOG`, System log, бэкап `./data`; README — рост диска
- [x] `envdoc_test``MAIL_LOG` в `buildScriptKeys`
### 2.2 Postqueue reconcile
- [x] `internal/postfix``QueueIDs` / парсер queue-id из `postqueue -p` + тест
- [x] `internal/store``ListQueuedOlderThan(cutoff)`
- [x] `internal/logtail` — periodic sweep (5 min), grace 2 min → `bounced`,
старт только после того, как tailer дочитал лог до конца
- [x] Тесты reconcile
### 2.3 Документация и риски
- [x] `architecture.md``/data/log`, reconcile; gap «container recreate» убран
- [x] `security.md` — риск «вечный queued» снят; на его месте — ложный `bounced`
и права на лог
- [x] CHANGELOG `[Unreleased]`
**Стенд (остаток, для оператора):** Docker на машине разработки недоступен —
сборка образа и старт контейнера не проверены. Проверить при выкате: `/data/log`
создаётся с нужными правами, панель читает `mail.log`, logrotate проворачивает
файл, `postqueue -p` читается из-под `panel`.
---
## Фаза 3 — Документация
**Модель:** Sonnet.
### 3.1 `documentation-plan.md` → development.md, затем delete
- [x] development.md § **Documentation**:
- [x] Documentation map (без archive; история = git + CHANGELOG)
- [x] User-facing deliverables
- [x] Maintaining documentation (§3 правила)
- [x] Verifying docs against code (полная таблица §2)
- [x] `architecture.md` шапка → development.md
- [x] Удалить `documentation-plan.md`
- [x] Retarget `roadmap.md`
### 3.2 `progress.md` → development.md, затем delete
- [x] development.md § **Resuming work**
- [x] development.md § **Model routing**
- [x] development.md § **Commits** (слить с Release build)
- [x] development.md § **Phase closure** (roadmap, не progress)
- [x] `agent-rules.mdc` → development.md
- [x] Удалить `progress.md`
- [x] Retarget все ссылки
### 3.3 Удалить `docs/archive/`
- [x] Удалить `docs/archive/specification-v1.0.md`
- [x] Удалить каталог `docs/archive/`
- [x] Убрать ссылки на archive из живых docs (CHANGELOG историю ниже 0.6.0 не трогать)
### 3.4 README
- [x] § Documentation: operator docs + roadmap (internal, Russian)
- [x] Без секции Archive
### 3.5 Проверка
- [x] `gofmt` / `vet` / `test`
- [x] grep живых ссылок на удалённые файлы
---
## Фаза 4 — Релизный коммит R1
**Модель:** Haiku / Sonnet. **Только по явной команде оператора.**
- [ ] CHANGELOG: `[Unreleased]``[1.0.0] - дата`, новая `[Unreleased]`
- [ ] `deploy/docker-compose.yml``ghcr.io/mixeme/selfpost:1.0.0`
- [ ] Удалить `implementation-plan.md` (если ещё есть)
- [ ] Makefile, `.github/workflows/release.yml`, `test/e2e/main_test.go` → development.md
- [ ] `roadmap.md` — секция v1.x **закрыта**; убрать «переместить в archive»
- [ ] Финальный grep ссылок
- [ ] Удалить этот файл (`v1.x-closure-plan.md`) в том же или следующем коммите
---
## Фаза 5 — Tag и push
**Оператор.** Явная команда.
```sh
git tag v1.0.0
git push origin v1.0.0
```
- [ ] `release.yml` — build amd64+arm64, e2e gate, push GHCR `1.0.0`
- [ ] compose tag и git tag совпадают
---
## Фаза 6 — Прод (оператор)
- [ ] `docker compose pull``1.0.0`
- [ ] Разлогин (cookie `__Host-`)
- [ ] Reverse-proxy передаёт `Host`
- [ ] `./data/log/` создался после upgrade
---
## Критерий «хвост v1.x закрыт»
- [ ] Все чек-листы фаз 1–4 отмечены
- [ ] `compose` = `1.0.0`, тег `v1.0.0` в репо
- [ ] Нет `implementation-plan.md`, `documentation-plan.md`, `progress.md`, `docs/archive/`, `v1.x-closure-plan.md`
- [ ] Нет активных ссылок на удалённые пути
- [ ] `development.md` — процесс + Documentation + model routing
- [ ] `roadmap.md` — единственный internal tracker
- [ ] Polling 5s/30s; mail.log в `/data`; reconcile; риск security снят
---
## Разбивка коммитов
1. **Polling** — panel.js, templates, architecture (+ CHANGELOG)
2. **mail.log + reconcile** — build, logtail, store, postfix, docs (+ CHANGELOG)
3. **Docs** — development.md, README, agent-rules; delete documentation-plan, progress, archive; retarget (+ CHANGELOG)
4. **Release 1.0.0** — CHANGELOG cut, compose, delete implementation-plan, roadmap closure, delete `v1.x-closure-plan.md`
5. **Tag/push** — оператор
+6 -6
View File
@@ -1,13 +1,13 @@
// Package e2e is the hermetic container e2e gate (plan C.4): it drives the // Package e2e is the hermetic container e2e gate: it drives the shipped
// shipped deploy/docker-compose.yml (plus a test-only override) exactly as an // deploy/docker-compose.yml (plus a test-only override) exactly as an
// administrator and their applications would, so the class of failure unit // administrator and their applications would, so the class of failure unit
// tests cannot see — broken container wiring — has one place to be caught // tests cannot see — broken container wiring — has one place to be caught
// before an image is published. // before an image is published.
// //
// It is a separate module on purpose (see ../../docs/implementation-plan.md, // It is a separate module on purpose (see ../../docs/development.md):
// item C.4): `go test ./...` in the main module never pulls this in, and its // `go test ./...` in the main module never pulls this in, and its test-only
// test-only dependencies (DKIM verification) never enter the shipped // dependencies (DKIM verification) never enter the shipped binaries' build
// binaries' build graph. // graph.
package e2e package e2e
import ( import (