docs: track every unimplemented review finding in the roadmap
test / test (push) Has been cancelled

Nothing from the 2026-08-19 code review now lives only in its plan file. Four
new roadmap items, all `candidate`:

- csrf-tokens — §4.4. The review recorded a decision to add per-session tokens
  but never turned it into a task, and security.md still argues the header
  checks suffice. The item says to settle which of the two stands first.
- template-data-typing — §7.1, map[string]any template payloads.
- structured-logging — §7.2 and §5.5, log/slog plus the per-package logf
  aliases.
- review-2026-08-followups — the small remainder: backup re-auth, the missing
  DMARC-service / handler / error-page / corrupted-backup tests, and the nine
  minor findings (§6.1, §6.2, §6.4, §7.4, §8.6, §9.3, §10.3-10.5) that were
  never carried into the phase tables.

§7.3 (build tags for the /proc reader) is deliberately not carried over: it was
declined during the pass, and both documents now say so, so it does not get
re-raised at the next review.

The plan's status table links each open row to its roadmap item, and the
roadmap's recommended-order note points back at the plan. Also unlinked two
CHANGELOG references to plan files deleted after those releases shipped; the
text stays, the dead links do not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-08-20 00:09:43 +03:00
co-authored by Claude Opus 5
parent ab2cd885c4
commit 1e4746e52f
3 changed files with 156 additions and 9 deletions
+10 -2
View File
@@ -103,6 +103,14 @@ rate limit is correct for a non-default level-1 window. Schema migration
### Changed ### Changed
- [roadmap.md](docs/roadmap.md) now carries everything the 2026-08-19 code
review left unimplemented, so no finding lives only in a plan file:
`csrf-tokens` (the recorded §4.4 decision that never became a task),
`template-data-typing`, `structured-logging`, and
`review-2026-08-followups` for the small remainder (backup re-auth, the
missing service/handler/error-page tests, and nine minor findings that were
never in the phase tables). The review plan's status table links each row to
its roadmap item; the one declined finding is marked as declined in both.
- Extracted `internal/configsafe` — the empty-value and forbidden-character - Extracted `internal/configsafe` — the empty-value and forbidden-character
check every generated config file shares. Each writer keeps its own forbidden check every generated config file shares. Each writer keeps its own forbidden
set next to the file it protects (an OpenDKIM table and a Postfix map break on set next to the file it protects (an OpenDKIM table and a Postfix map break on
@@ -414,7 +422,7 @@ Plex. Upgrading from 1.2.x is a tag bump; no migration.
- docs: agreed roadmap item **queue-retries** — show this Postfix's retry - docs: agreed roadmap item **queue-retries** — show this Postfix's retry
policy (first delay, backoff cap, queue lifetime) on Mail queue and on a policy (first delay, backoff cap, queue lifetime) on Mail queue and on a
delivery's history, reading `postconf -h` once at panel start so a manual delivery's history, reading `postconf -h` once at panel start so a manual
override is visible. Plan: [docs/plans/queue-retries.md](docs/plans/queue-retries.md). override is visible. Plan: `docs/plans/queue-retries.md` (removed once shipped).
Explanation only; no attempt counter and no panel knobs. Not yet Explanation only; no attempt counter and no panel knobs. Not yet
implemented. implemented.
@@ -606,7 +614,7 @@ Plex. Upgrading from 1.2.x is a tag bump; no migration.
RBAC in the doc-alignment pass above). Corrected stale RBAC in the doc-alignment pass above). Corrected stale
`admin.dmarc_report_email` references in `admin.dmarc_report_email` references in
[roadmap.md](docs/roadmap.md) and [roadmap.md](docs/roadmap.md) and
[docs/plans/dmarc-reports.md](docs/plans/dmarc-reports.md) to the setting's `docs/plans/dmarc-reports.md` (removed once shipped) to the setting's
actual home after migration `0005`. No behaviour change. actual home after migration `0005`. No behaviour change.
- panel: code-review P6 cleanup — the unused `auth.RequireGlobal` middleware is - panel: code-review P6 cleanup — the unused `auth.RequireGlobal` middleware is
+23 -7
View File
@@ -336,18 +336,18 @@ SelfPost — зрелый, хорошо структурированный пр
| 14 | Ссылки на spec в build/deploy | сделано: `build/`, `deploy/`, миграции, workflow | | 14 | Ссылки на spec в build/deploy | сделано: `build/`, `deploy/`, миграции, workflow |
| 15 | `dmarc-ingest` symlink | не требовалось — уже описан в `architecture.md` | | 15 | `dmarc-ingest` symlink | не требовалось — уже описан в `architecture.md` |
| 16 | Ссылки в roadmap | сделано | | 16 | Ссылки в roadmap | сделано |
| 17 | Типизированные template data structs | **не сделано** | | 17 | Типизированные template data structs | **не сделано** → `template-data-typing` |
| 18 | Миграция на `log/slog` | **не сделано** | | 18 | Миграция на `log/slog` | **не сделано** → `structured-logging` |
| 19 | Build tags для `machine.go` | отклонено, см. ниже | | 19 | Build tags для `machine.go` | отклонено, см. ниже |
| 20 | Guide URL → `{{.SourceURL}}` | сделано | | 20 | Guide URL → `{{.SourceURL}}` | сделано |
| 21 | Тесты DMARC service layer | **не сделано** | | 21 | Тесты DMARC service layer | **не сделано** → `review-2026-08-followups` |
| 22 | Тесты хендлеров | **не сделано** | | 22 | Тесты хендлеров | **не сделано** → `review-2026-08-followups` |
| 23 | postfix/dmarc injection cases | сделано | | 23 | postfix/dmarc injection cases | сделано |
| 24 | `validate_email_test` | сделано | | 24 | `validate_email_test` | сделано |
| 25 | Тесты error pages / битого бэкапа | **не сделано** | | 25 | Тесты error pages / битого бэкапа | **не сделано** → `review-2026-08-followups` |
| 26 | CSP `unsafe-inline` | не требовалось — директивы в CSP нет | | 26 | CSP `unsafe-inline` | не требовалось — директивы в CSP нет |
| 27 | bcrypt cost 12 | сделано, включая setup-аккаунт (`auth.BcryptCost`) | | 27 | bcrypt cost 12 | сделано, включая setup-аккаунт (`auth.BcryptCost`) |
| 28 | Re-auth перед бэкапом | **не сделано** | | 28 | Re-auth перед бэкапом | **не сделано** → `review-2026-08-followups` |
| 29 | Accessibility help drawer | сделано | | 29 | Accessibility help drawer | сделано |
| 30 | Ограничение `postqueue -p` | сделано: таймаут 5 с + лимит вывода 4 МиБ | | 30 | Ограничение `postqueue -p` | сделано: таймаут 5 с + лимит вывода 4 МиБ |
| 31 | `internal/configsafe` | сделано | | 31 | `internal/configsafe` | сделано |
@@ -361,9 +361,25 @@ SelfPost — зрелый, хорошо структурированный пр
**Открытый вопрос.** §4.4: решение «добавить per-session CSRF-токен» записано, **Открытый вопрос.** §4.4: решение «добавить per-session CSRF-токен» записано,
но задачи под него в таблицах фаз нет, и токенов в коде нет. Нужно либо но задачи под него в таблицах фаз нет, и токенов в коде нет. Нужно либо
завести задачу, либо вернуть §4.4 в статус осознанного компромисса. реализовать токены, либо вернуть §4.4 в статус осознанного компромисса и
оставить это решение в одном месте — в `security.md`.
**Вне фазовых таблиц** (в план не переносились и не делались): §5.5, §6.1, **Вне фазовых таблиц** (в план не переносились и не делались): §5.5, §6.1,
§6.2, §6.4, §7.4, §8.6, §10.3, §10.4, §10.5, а также §9.3–9.4 сверх задач §6.2, §6.4, §7.4, §8.6, §10.3, §10.4, §10.5, а также §9.3–9.4 сверх задач
23–25. 23–25.
---
## Что перенесено в roadmap
Всё незакрытое заведено в [roadmap.md](../roadmap.md) — этот файл больше не
единственное место, где живут остатки:
| Пункт плана | Элемент roadmap |
|---|---|
| §4.4 CSRF-токен | [csrf-tokens](../roadmap.md#csrf-tokens) |
| Задача 17 (§7.1) | [template-data-typing](../roadmap.md#template-data-typing) |
| Задача 18 (§7.2), §5.5 | [structured-logging](../roadmap.md#structured-logging) |
| Задачи 21, 22, 25, 28 и §6.1, §6.2, §6.4, §7.4, §8.6, §9.3, §10.3–10.5 | [review-2026-08-followups](../roadmap.md#review-2026-08-followups) |
| Задача 19 (§7.3) | не переносится — отклонено, обоснование выше |
+123
View File
@@ -32,6 +32,10 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md).
| contributing | `CONTRIBUTING.md` | candidate | — | — | | contributing | `CONTRIBUTING.md` | candidate | — | — |
| inbound-antispam-panel | Inbound antispam journal + allow/deny lists | agreed | 1/12 | [plans/inbound-antispam-panel.md](plans/inbound-antispam-panel.md) | | inbound-antispam-panel | Inbound antispam journal + allow/deny lists | agreed | 1/12 | [plans/inbound-antispam-panel.md](plans/inbound-antispam-panel.md) |
| inbound-quarantine | Inbound spam quarantine (hold / review / release) | candidate | — | [plans/inbound-quarantine.md](plans/inbound-quarantine.md) | | inbound-quarantine | Inbound spam quarantine (hold / review / release) | candidate | — | [plans/inbound-quarantine.md](plans/inbound-quarantine.md) |
| csrf-tokens | Per-session CSRF tokens on state-changing forms | candidate | — | — |
| template-data-typing | Typed structs for template data instead of `map[string]any` | candidate | — | — |
| structured-logging | `log/slog` with levels and fields | candidate | — | — |
| review-2026-08-followups | Remaining findings of the 2026-08-19 code review | candidate | — | [plans/code-review-2026-08.md](plans/code-review-2026-08.md) |
| schema-squash | Squash SQLite migrations into a 2.x baseline | **2.x** | — | — | | schema-squash | Squash SQLite migrations into a 2.x baseline | **2.x** | — | — |
**Recommended order** (not binding): the next feature is **Recommended order** (not binding): the next feature is
@@ -42,6 +46,14 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md).
queue-retries in `[1.3.1]`; the 2026-08-13 full-tree review follow-ups are in queue-retries in `[1.3.1]`; the 2026-08-13 full-tree review follow-ups are in
`[1.3.0]`. Candidates need explicit agreement before they join the queue. `[1.3.0]`. Candidates need explicit agreement before they join the queue.
The 2026-08-19 code review ([plans/code-review-2026-08.md](plans/code-review-2026-08.md))
is closed except for four items, all listed above and none of them urgent:
[csrf-tokens](#csrf-tokens), [template-data-typing](#template-data-typing),
[structured-logging](#structured-logging) and
[review-2026-08-followups](#review-2026-08-followups). What that review
already produced is in [CHANGELOG.md](../CHANGELOG.md) `[Unreleased]`; the
per-task status table is at the end of its plan file.
After a context reset, pick an item marked `agreed` or `in progress`, then work After a context reset, pick an item marked `agreed` or `in progress`, then work
the **Implementation checklist** in its linked plan. The `Progress` column above the **Implementation checklist** in its linked plan. The `Progress` column above
is `done/total` checklist steps in that plan ([development.md](development.md) is `done/total` checklist steps in that plan ([development.md](development.md)
@@ -163,6 +175,117 @@ falls back to `postmaster@SELFPOST_HOSTNAME`.
--- ---
## csrf-tokens
**Goal:** a per-session CSRF token on every state-changing form, checked
server-side in addition to the `Sec-Fetch-Site` / `Origin` checks the panel
relies on today.
**Boundary:** defence in depth, not a replacement — the header checks stay and
keep covering requests that carry no form. The token is bound to the existing
session record; no new session model, no cookie changes.
**Done when:** every POST form and htmx fragment that changes state carries a
token, a missing or mismatched token is refused with a test that proves it, and
[security.md](security.md) describes the layered model instead of arguing that
the header checks alone are sufficient.
**Dependencies / risks:** the current documented decision in
[security.md](security.md) is that header checks suffice; this item exists
because the 2026-08-19 review recorded the opposite decision without ever
turning it into a task (§4.4). Confirm which of the two stands before writing
code — the honest outcomes are "implement tokens" or "keep headers and say so
in one place only". Token plumbing through htmx swaps is where the work is.
**Version:** `1.x` MINOR; `candidate`.
---
## template-data-typing
**Goal:** replace `map[string]any` template payloads in `internal/web/handlers`
with typed structs (`DomainDetailData`, `SettingsData`, …), so a renamed or
mistyped key is a compile error instead of a silently empty page section.
**Boundary:** handler-to-template plumbing only; no template redesign, no route
or behaviour change. `renderDomainDetail` (~40 keys) is the worst case and the
one that motivates the item.
**Done when:** the page handlers pass structs, the shared footer/base fields
still arrive on every page, and the template guard tests pass unchanged.
**Dependencies / risks:** mechanical but wide — it touches every
`handlers_*.go` and every template that reads those keys; a missed key is
invisible unless a test renders that page. Worth pairing with the handler
tests in [review-2026-08-followups](#review-2026-08-followups) so the pages
are covered before they are rewired. Review estimate: 2–3 hours (§7.1).
**Version:** no bearing on semver; `candidate`.
---
## structured-logging
**Goal:** move from `log.Printf` to `log/slog` — levels, structured fields, and
one logger injected through constructors instead of a package-level `logf` in
five packages (§5.5, §7.2).
**Boundary:** internal logging only. The log destination stays stdout/stderr
under supervisord; no log shipping, no new dependency, and the operator-visible
lines the Status page and the log tailer parse must keep parsing.
**Done when:** packages take a `*slog.Logger`, the per-package `logf = log.Printf`
aliases are gone, levels are used deliberately (an error is not an info), and
the panel's own journal/log-tailer expectations still hold.
**Dependencies / risks:** the log tailer reads Postfix's log, not the panel's,
so the risk is contained — but anything that greps the panel's output (e2e,
operator habits) sees a new format. Review estimate: 3–4 hours (§7.2).
**Version:** no bearing on semver; `candidate`.
---
## review-2026-08-followups
**Goal:** the leftovers of the 2026-08-19 code review that are too small to be
roadmap items of their own. The full findings and the per-task status table are
in [plans/code-review-2026-08.md](plans/code-review-2026-08.md).
| § | Item |
|---|---|
| §4.7 | Re-authenticate (current password) before a backup download |
| §9.2 | Tests for the DMARC service layer (`service.go`, `addresses.go`) |
| §9.2 | Tests for the apps / domains / users / dmarc handlers |
| §9.4 | Tests: error page templates, corrupted backup archive, delivery-log pagination edges |
| §9.3 | Thin spots: `store/stats_test` zero-traffic case, concurrent store operations |
| §6.1 | `secretfile` accepts an empty password at library level — guard or document |
| §6.2 | Log-rotation fingerprint collision in `logtail/offset.go` — include the inode |
| §6.4 | `parsePage` does not clamp to `lastPage` |
| §7.4 | `dnscheck` resolver fans out four parallel queries instead of falling back — decide or document |
| §8.6 | `dev/workflow.md` still names an outdated base version |
| §10.3 | `.pair` vs `.split`: two class names for one layout |
| §10.4 | Two `back_link` elements in `dmarc_domain.html` |
| §10.5 | `initShowWhen` is not re-run after an htmx swap (safe today, fragile) |
**Boundary:** no feature work and no behaviour change an operator would notice,
apart from the backup re-auth prompt. Anything here that grows past an hour
becomes its own roadmap item instead.
**Done when:** each row is either implemented or struck with a written reason,
and the status table in the plan file says which.
**Dependencies / risks:** the test rows overlap
[template-data-typing](#template-data-typing) — write the handler tests first
if both are taken up. §7.3 (build tags for the `/proc` reader) was **declined**
during the review pass and is deliberately absent: that code is parameterised by
root, has no platform-specific calls, already degrades gracefully without
`/proc`, and tagging it `linux` would only delete cross-platform test coverage.
**Version:** no bearing on semver; `candidate`.
---
## schema-squash ## schema-squash
**Goal:** when 2.x is cut, stop shipping the 1.x migration chain in the binary **Goal:** when 2.x is cut, stop shipping the 1.x migration chain in the binary