Files
mopac-pmo/archive/REPORT-20260829-1700-crush.md
T

106 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.