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

5.4 KiB
Raw Permalink Blame History

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: Makefiledev.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.