Commit Graph
159 Commits
Author SHA1 Message Date
Lucas Rayan fc8bbaa3ce #3: F0/F1 validation - embedded-Postgres E2E test + close 3 known gaps
Merges an independent validation pass on F0/F1: a real end-to-end test
(embedded Postgres, no Docker required) driving a live SMTP session
against the exact server main.go assembles, plus fixes for three gaps
the test's own analysis found - folder/mailbox counters not updated,
mailbox quota never checked at RCPT, and the aliases table never
consulted for recipient resolution. See commits 097fa37 and 2f73898
for full details.
2026-07-23 12:14:33 -03:00
Lucas Rayan 2f7389841d #3: close 3 known F1 gaps - folder/mailbox counters, quota-at-RCPT, alias fan-out
Per feature/3-f0-f1-validation's own gap analysis (commit 097fa37):

- InboundMessageRepository.Persist now updates folders.unread_count,
  folders.total_count, and mailboxes.used_bytes in the same transaction
  as the message insert (PLAN.md section 6.1 "++counters" was a no-op
  before this).
- MailboxRecord carries QuotaBytes/UsedBytes; ResolveRecipient rejects
  RCPT with 452 4.2.2 when a target mailbox has already reached its
  quota (used_bytes >= quota_bytes), before DATA - never silently
  accepting mail into a full mailbox.
- MailboxRepository.ResolveDeliveryTargets tries a direct mailbox match
  first, then falls back to the aliases table (exact source_address
  match takes precedence over a domain catch-all). One RCPT TO can now
  fan out to multiple mailboxes; alias destinations that don't resolve
  to an internal active mailbox are silently skipped, since outbound/
  external forwarding doesn't exist yet. postmaster@/abuse@/dmarc@-style
  aliases now actually deliver.

The E2E test's counters_updated subtest flips from a documented KNOWN
GAP (t.Skipf) to a hard assertion now that it passes, and gains two new
subtests (quota_rejected_when_mailbox_full, alias_resolves_to_destination_mailbox)
covering the other two fixes end-to-end. New unit and Postgres
integration test coverage added for all three fixes at every layer.

Verified: full suite green on Windows (unit + E2E via embedded
Postgres, no Docker needed) and via WSL Docker against the real
docker-compose Postgres (all infrastructure/postgres and diskstorage
integration tests, not just E2E). lang-lint clean.
2026-07-23 11:36:03 -03:00
Lucas RayanandClaude Fable 5 097fa373e1 #3: add embedded-Postgres E2E test validating F1 inbound SMTP against PLAN acceptance criteria
Boots a real Postgres (no Docker), applies the real migration and seed,
starts the real go-smtp server wired as main.go does, and drives a live
SMTP session. Asserts: 550 for unknown RCPT, durable EML + row with
uid_next allocation, sha256 disk/db match, outbox event, blob dedup with
ref_count. Documents the counters gap (PLAN 6.1) as an explicit skip.

Phase: F1 validation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 11:23:19 -03:00
Lucas Rayan 6980a08516 #2: F1 sub-project 1 - inbound SMTP listener with EML storage
Merges the first F1 sub-project: a real :25 SMTP listener for the Go
protocols service with STARTTLS, Postgres-backed recipient resolution,
content-hash-deduplicated blob storage, and transactional persistence.
See commit e501cdc for full details.
2026-07-23 11:16:47 -03:00
Lucas Rayan e501cdc69e #2: implement F1 sub-project 1 - inbound SMTP listener with EML storage
Adds a real :25 SMTP listener to the Go protocols service, giving F1's
"Nucleo SMTP" milestone its first working sub-project: application,
infrastructure, and presentation layers on top of F0's domain-only
Clean Architecture.

- ProcessInboundEmailUseCase resolves recipients against Postgres and
  persists accepted mail (blob dedup by content hash, transactional
  UID allocation + outbox event, never sending 250 OK before durable
  commit per PLAN.md section 6.1).
- Postgres repositories via pgx/v5 (replacing lib/pq entirely).
- Local-disk blob storage with content-hash dedup, rename-before-commit
  ordering so a DB row can never reference a file that isn't actually
  on disk.
- A local-file TLS cert provider for STARTTLS, with an ephemeral
  self-signed fallback so a missing/misconfigured cert degrades
  gracefully instead of crash-looping the service (PLAN.md section 8.4).
- A github.com/emersion/go-smtp Backend/Session adapting real SMTP
  traffic to the use case: unknown recipients get 550 5.1.1 before
  DATA, duplicate RCPT TO for the same address is deduplicated, TLS is
  hardened to the project's required PFS-only cipher/curve policy.
- A dev-seed script and Makefile target for local testing.

Explicitly out of scope (separate follow-up sub-projects): alias
resolution, SPF/DKIM/DMARC/ARC verification, Rspamd/ClamAV, Submission
ports with SASL auth, DKIM signing, outbound delivery, and the real
Traefik acme.json cert watcher.

Verified live end-to-end via WSL Docker: the full compose stack (db,
redis, migrate, auth, storage, protocols, web) reaches healthy, and a
real swaks SMTP session is accepted and durably persisted with correct
sequential UIDs, while an unknown recipient is correctly rejected with
550 5.1.1 and zero rows persisted. Built via 10 sequential tasks under
subagent-driven development, each independently reviewed, plus a final
whole-branch review that caught and fixed a TLS-crash bug, a blob
commit-ordering bug, missing TLS hardening, and missing CI integration
coverage before merge.

Known follow-up, not blocking: email_messages' partition only covers
July 2026 and needs a partition-management job before 2026-08-01.
2026-07-23 08:41:31 -03:00
Lucas Rayan 7c91182111 #1: fix webmail healthcheck - Next standalone binds IPv4-only, wget resolved localhost to ::1
Verified live via WSL Docker (docker compose up --wait): every other F0
service came up healthy, but web stayed unhealthy. Root cause was two
compounding issues, both invisible to static review:

1. Docker auto-sets HOSTNAME to the container ID; Next's standalone
   server.js honors that env var and binds only to it instead of all
   interfaces. Fixed by explicitly setting ENV HOSTNAME=0.0.0.0 in the
   Dockerfile, after Docker's automatic assignment.
2. Even bound to 0.0.0.0 (IPv4), the healthcheck's `wget http://localhost`
   resolved localhost to ::1 (IPv6) first inside the container, where
   nothing was listening. Fixed by pointing the healthcheck at
   127.0.0.1 explicitly instead of localhost.

Full stack (db, redis, migrate, auth, storage, protocols, web) now
reaches healthy end-to-end under docker compose up --wait.
2026-07-23 06:51:28 -03:00
Lucas Rayan 096263e0e9 #1: F0 foundation - monorepo, migrations, Go domain layer, service skeletons, CI
Merges the F0 milestone from PLAN.md: monorepo scaffolding, initial Postgres
schema migration, the Go protocols domain layer with unit tests, health-checked
auth/storage/webmail service skeletons, local docker-compose.yaml, language
lint, and CI workflow.
2026-07-23 06:32:56 -03:00
Lucas Rayan eb09fa0ced #1: implement F0 foundation - monorepo, migrations, Go domain layer, service skeletons, CI
Stands up the LambdaMail monorepo per PLAN.md's F0 milestone: local dev
scaffolding (.env.example, Makefile, .gitignore excluding PLAN.md/docs/
.superpowers/), the full initial Postgres schema as a golang-migrate
migration, the complete Go domain layer for services/protocols (value
objects, entities, domain events - zero external imports beyond
github.com/google/uuid) with unit tests, health-checked service skeletons
for auth/storage (TypeScript) and webmail (Next.js), a local
docker-compose.yaml wiring all services together, a language-enforcement
lint script, and a GitHub Actions CI workflow.

Built via 11 sequential tasks under subagent-driven development, each with
an independent implementer + reviewer cycle. Fixes applied during review:
DomainName.IsPunycode() added to satisfy the promised interface; ioredis
default-import typing under NodeNext; next.js bumped 15.0.3->15.5.21 to
resolve high/critical CVEs; lang-lint.mjs corrected to not flag its own
banned-terms list; web service healthcheck added so compose --wait
actually waits on it; webmail Dockerfile's missing public/ directory:
protocols go.mod version aligned with CI's Go 1.22 pin.

Known follow-ups for later phases (not blocking F0): go-arch-lint isn't
wired into CI yet; live docker compose up and migration apply have not
been exercised in this dev environment (no Docker/psql available here)
and will get their first real run in CI, which does have Docker.
2026-07-23 05:05:46 -03:00
Lucas Rayan Guerra f219b28075 Initial commit 2026-07-23 02:18:24 -03:00