feat: log-tailer offset persistence + in-flight L2 rate-limit accounting (code-review.md § Phase 3)

- logtail: persist the read position (offset + fingerprint of the log's
  first 512 bytes) in a new logtail_state table (migration 0003) and
  resume from it on start, so delivery lines written while the panel was
  down are parsed instead of skipped and their send-log rows no longer
  stay "queued" forever. Fingerprint mismatch (rotated/recreated while
  down) reads the file from the start — re-parsing is idempotent; a
  first-ever start with nothing stored still begins at end-of-file.
  Writes are throttled to one per 5s, forced on rotation and shutdown.

- milter: count messages that passed the level-2 check but have not
  reached the send log yet (internal/milter/inflight.go), so concurrent
  SMTP sessions cannot each spend the same last slot. A literal
  count+insert transaction, as the review suggested, is not possible:
  the count happens at MAIL FROM and the insert at end-of-message.
  Reservations are released after the insert, on ABORT, and after a
  10-minute TTL — a client that drops mid-transaction must not be able
  to hold a slot, since the limiter is fail-open by design.

Docs: architecture.md (log tailer, persistence, L2 counting),
security.md and roadmap.md (restart gap closed, container recreate
remains), CHANGELOG, progress.md, code-review.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 17:12:37 +03:00
parent 0093878eea
commit a92d583053
15 changed files with 683 additions and 40 deletions
+17
View File
@@ -5,6 +5,23 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
## [Unreleased]
### Fixed
- Log-tailer resumes where it stopped instead of jumping to end-of-file on
every start (phase 3, `docs/code-review.md`): the read position and a
fingerprint of the log's head are persisted (`logtail_state`, migration
`0003`), so delivery lines written while the panel was down are parsed and
their send-log rows no longer stay `queued` forever. A log that changed
identity while the panel was down is read from the start; a first-ever start,
with nothing stored, still begins at the end. Container recreate remains a
gap — `mail.log` is not in `/data` (`docs/security.md`).
- Level-2 rate limit no longer overshoots under concurrency: messages that
passed the check at MAIL FROM but have not reached the send log yet are
counted alongside the stored rows (`internal/milter/inflight.go`), so
parallel SMTP sessions cannot each spend the same last slot. Slots are
released at end-of-message, on ABORT, and after a 10-minute TTL, so a client
that drops mid-transaction cannot hold one — the limiter stays fail-open.
### Changed
- Phase 1 doc/code hygiene (`docs/code-review.md`): removed ~30 stale