106 lines
5.4 KiB
Markdown
106 lines
5.4 KiB
Markdown
# REPORT — 2026-08-29 17:00 — Crush: mopac-imap-go (Redmine 501)
|
||
|
||
- task: new repo `ukrrs/mopac-imap-go` — stdlib-only Go IMAP client
|
||
(CLI + library): RFC 2177 IDLE watch, fetch+parse (headers, text
|
||
body, attachments to spool), mark seen, SMTP passthrough send.
|
||
Env `IMAP_HOST/IMAP_USER/IMAP_PASS`. Fake-server tests. Feeds the
|
||
fleet's email-driven intake.
|
||
- result: DONE — 12 atomic conventional commits pushed to
|
||
`origin/main` (https://git.knownelement.com/ukrrs/mopac-imap-go,
|
||
private, UKRRS org), HEAD `66a9135`, branch in sync, tests green,
|
||
smoke PASS. NO Redmine writes performed.
|
||
|
||
## 1. What shipped
|
||
|
||
Repo created via `tea` (UKRRS/mopac-imap-go, private). Sibling-layout
|
||
to `mopac-redmine-go` at `~/projects/meta/MOPAC/mopac-imap-go`:
|
||
`Makefile` → `dev.sh` → digest-pinned `golang:1.26-bookworm` builder
|
||
(`golang@sha256:e8c859f5…`) — every build/vet/test ran in Docker; the
|
||
host never ran a Go toolchain.
|
||
|
||
First commit ships full AGPLv3 LICENSE (661 lines, verbatim from the
|
||
sibling corpus). Module: `git.knownelement.com/ukrrs/mopac-imap-go`,
|
||
go 1.26, zero third-party dependencies.
|
||
|
||
### Library packages
|
||
|
||
- `imap/` — IMAP4rev1 client: implicit-TLS or plain dial with greeting
|
||
validation; quoted-string LOGIN (password only ever inside the
|
||
command, never in errors); SELECT parsing
|
||
EXISTS/RECENT/UNSEEN/UIDVALIDITY; FETCH `(UID FLAGS BODY.PEEK[])`
|
||
with a literal-inlining wire reader (payloads containing `{N}\r\n`-
|
||
looking bytes parse correctly — pinned by test); STORE
|
||
`+FLAGS.SILENT (\Seen)`; RFC 2177 IDLE with typed events
|
||
(exists/recent/expunge/tick/other), tick-driven re-idle, and a Stop
|
||
that consumes the tagged DONE completion so the stream stays clean
|
||
(a subtle readLoop/select race was found by `-race`-adjacent flake
|
||
hunting and fixed: after stop, readLoop keeps reading until the
|
||
tagged line). Sentinel errors: unreachable/protocol/auth/mailbox/
|
||
timeout, one-line messages.
|
||
- `mail/` — RFC 5322 + MIME parse: RFC 2047 header decoding, QP/base64
|
||
transfer decoding, recursive multipart (mixed/alternative nesting),
|
||
attachments via Content-Disposition filename or Content-Type name.
|
||
Non-UTF-8 charsets pass through raw (no x/text in this repo).
|
||
`Spool()` writes `SPOOL_DIR/<sha256-12>/` containing `raw.eml`,
|
||
`meta.json` (seq, ids, decoded headers, attachment index) and
|
||
`attachments/<filename>` — content-hashed dirs make re-spooling
|
||
idempotent across seq renumbering; hostile filenames reduced to
|
||
basename (escape attempt pinned by test); 0700 dirs / 0600 files.
|
||
- `mailer/` — SMTP passthrough: raw bytes on the wire unmodified;
|
||
STARTTLS when offered, AUTH PLAIN when credentials set; sentinels
|
||
unreachable/auth/rejected.
|
||
|
||
### CLI (`mimap`) + config
|
||
|
||
- `internal/config`: `IMAP_HOST/IMAP_USER/IMAP_PASS` required (env
|
||
wins over an optional 0600 `KEY=VALUE` env file, looser-than-0600
|
||
refused before read); defaults 993/TLS/INBOX, `SPOOL_DIR=spool`,
|
||
SMTP set optional with `SMTP_FROM` falling back to the IMAP user.
|
||
Errors name keys, never values.
|
||
- `internal/cli`: `watch` (backlog fetch → IDLE; per message: spool →
|
||
JSON line on stdout → mark `\Seen`, so a crash re-delivers
|
||
idempotently instead of dropping; IDLE stopped before any FETCH;
|
||
ticks re-SELECT so a missed EXISTS self-heals; SIGINT/SIGTERM →
|
||
clean LOGOUT, exit 0), `check` (mailbox state as JSON), `send`
|
||
(`--to` repeatable, `--file PATH|-` → SMTP passthrough). Exit
|
||
codes 0/1/2 (ok/usage/protocol) matching the mred convention.
|
||
|
||
## 2. Verification
|
||
|
||
- TDD throughout: each feature commit landed test-first against
|
||
protocol-correct fakes (`internal/fakeimap` self-test pins every
|
||
wire exchange incl. literal FETCH bodies and IDLE push;
|
||
`internal/fakesmtp` pins AUTH/530/dot-unstuffing).
|
||
- `./dev.sh check` (build+vet+test) green; `go test -race -count=3
|
||
./...` green after fixing two real concurrency findings (DONE-
|
||
completion race in idle.go; unsynchronized test buffer); imap
|
||
package hammered `-count=10`.
|
||
- `./dev.sh smoke`: builds the real `bin/mimap` in the pinned builder
|
||
and drives it across process boundaries against the fakes —
|
||
`check` reports backlog, `send` relays, `watch` produces backlog +
|
||
live-IDLE-push JSON lines, 2 spool dirs, 2×`\Seen`, exit 0 on
|
||
SIGINT. Output: `smoke: PASS`.
|
||
|
||
## 3. Commits (all on origin/main)
|
||
|
||
66a9135 docs: README with CLI semantics, spool/intake contract, layout
|
||
019be0d test: end-to-end smoke of the real binary against fakes
|
||
ef2aec2 feat(cli): mimap watch/check/send over the library
|
||
d0a38b5 feat(config): env + 0600 env-file connection settings
|
||
4f5a43a feat(mailer): SMTP passthrough send with fake-server tests
|
||
2865718 feat(mail): spool messages and attachments to disk
|
||
dd1a462 feat(mail): parse RFC 5322 messages with MIME walk
|
||
444f297 feat(imap): RFC 2177 IDLE with clean Stop and re-idle
|
||
9d4ae78 feat(imap): fetch raw messages and mark seen
|
||
435fd81 feat(imap): client core — connect, login, select, logout
|
||
8552c9a test: in-process fake IMAP4rev1 server for client tests
|
||
f6332bf chore: bootstrap module (AGPLv3, docker dev harness)
|
||
|
||
## 4. Notes / limitations
|
||
|
||
- Charset conversion beyond UTF-8/ASCII is raw passthrough (stdlib
|
||
only); documented in package doc and README.
|
||
- Fake IMAP is one-mailbox (INBOX), no EXPUNGE push; the client still
|
||
handles EXPUNGE events defensively via re-SELECT.
|
||
- No Redmine API calls were made from this task, per instructions.
|