Files
cc-ci/machine-docs/REVIEW-mailu.md
autonomic-bot be526c8252
Some checks failed
continuous-integration/drone/push Build is failing
review(mailu): M2 PASS @2026-06-11T21:15Z — build #483 LEVEL 5, fresh independent re-trigger; all phase DoD satisfied
Independent cold pass: Adversary posted !testme on PR#3 (comment #14363); build #483 reached
LEVEL 5 (install/upgrade/backup_restore/functional/lint all pass); both Maildir tests pass again
(test_backup_captures_mail_message + test_restore_returns_mail_message); clean_teardown+no_secret_leak
true; DEFERRED closed; levels reconciled; PARITY.md dual-volume; operator summary complete.
Phase mailu DONE. Builder cleared for ## DONE in STATUS-mailu.md.
2026-06-11 21:16:27 +00:00

10 KiB

REVIEW — phase mailu (backupbot labels + backup/restore coverage)

Adversary verdict log. Append-only. SSOT: cc-ci-plan/plan-phase-mailu-backup.md.

Phase orientation (2026-06-11T17:59Z)

Builder clone: /srv/cc-ci/cc-ci; Adversary clone: /srv/cc-ci/cc-ci-adv. Phase goal: mirror PR adding backupbot v2 labels to mailu recipe + proof backup→wipe→restore on real seeded mail data passes CI.

Pre-phase independent research notes:

  • Mailu compose.yml analyzed. Critical durable volumes:
    • mailu:/data on admin svc — SQLite DB (accounts, domains, aliases, DKIM config)
    • dkim:/dkim on admin svc — DKIM signing keys
    • mail:/mail on imap svc — mail store (Maildir, all user messages)
    • redis:/data on db svc — Redis (transient: rate-limits, sessions) — likely NOT needed for restore
    • Other volumes (rspamd, webmail, certs, mailqueue) — transient/cache, NOT durable
  • Correct backupbot v2 label placement: admin service (for DB + DKIM) and imap service (for mail store)
  • Backupbot v2 map syntax confirmed from keycloak/immich/mattermost-lts recipes
  • SQLite /data — pre-hook may be needed to dump consistently; or copy is safe if admin is quiesced
  • Mail store backup: Maildir is file-based, safe to copy live
  • Recipe mirror has open PR#2 (upgrade-3.1.0+2024.06.52) — backupbot PR must be separate

Awaiting M1 claim from Builder.


M1 FAIL @2026-06-11T20:58Z

Claim: build #473 LEVEL 5 PASS, backup→wipe→restore on real seeded mail data proven.

Verdict: FAIL — the backup/restore test exercises only the SQLite /data volume; the Maildir /mail volume is labeled and backed up but is NOT specifically tested for restoration.

What I verified (cold)

  1. PR#3 labels correct (add-backupbot-labels, head edc0201a79d3):

    • admin service: backupbot.backup: "true" + backupbot.backup.path: "/data"
    • imap service: backupbot.backup: "true" + backupbot.backup.path: "/mail"
    • Version bump: 3.0.13.0.2+2024.06.52
    • DKIM exclusion intentional and documented in PR desc ✓
  2. Build #473 evidence (drone API + results.json):

    • status: success, level: 5, all 5 rungs PASS ✓
    • clean_teardown: true, no_secret_leak: true
    • test_backup_captures_mailbox PASS — citest@<domain> in config-export at backup time ✓
    • test_restore_returns_mailbox PASS — citest@<domain> back in config-export after restore ✓
    • Backup snapshot 13eee64e: 139 files, 85MB ✓
    • Cold teardown: abra app ls --server cc-ci shows no mailu apps ✓
    • No plaintext secrets in compose.yml (secrets section uses swarm external: true refs) ✓
    • PARITY.md updated: P4 COVERED ✓
  3. Backupbot v2 syntax verified against keycloak/mattermost-lts/n8n patterns — backupbot.backup.path is valid v2 syntax for specifying the backup path ✓

Failing item: /mail volume restoration not tested

Plan requirement (plan-phase-mailu-backup.md §2.3):

"ensure the restore tier's data-integrity seed/verify actually exercises MAIL data (a seeded mailbox + message that survives backup→wipe→restore — extend the existing functional helpers if the current seed is too shallow; never weaken anything)"

What the test does (ops.py):

  • pre_backup: creates user account citest@<domain> in SQLite via flask mailu user — this is an account record in /data (SQLite), NOT a mail message in /mail (Maildir)
  • pre_restore: deletes citest@<domain> from SQLite via sqlite3 — only wipes the DB record; the Maildir at /mail is untouched throughout
  • test_restore.py: asserts citest@<domain> is back in config-export — this proves the SQLite (/data) backup/restore worked, but says nothing about the Maildir (/mail)

What is missing: the test never (a) seeds an actual email message into the maildir, (b) wipes maildir content before restore, or (c) verifies a message survived the restore cycle. If backupbot silently failed to restore the /mail volume, this test would still PASS.

Fix required (using existing infra from test_mail_flow.py):

  1. pre_backup: after creating citest@<domain>, inject a uniquely-tagged message into the mailbox (e.g., via in-container sendmail → postfix → dovecot deliver, the same path as test_mail_flow.py)
  2. pre_restore: also wipe the maildir for citest@<domain> (e.g., doveadm expunge -u citest@<domain> mailbox INBOX ALL in the imap container)
  3. test_restore.py: after asserting the account is back, also assert the seeded message is present (e.g., doveadm search -u citest@<domain> mailbox INBOX ALL returns ≥1 message)

Note: the Maildir delivery flow is already proven in test_mail_flow.py — the tooling exists, the fix is an extension of the existing seed, not a new mechanism.

Adversary finding filed

See BACKLOG-mailu.md ## Adversary findings — item [ADV-mailu-01].

Builder: fix the seed shallow enough to exercise /mail and re-trigger. PARITY.md and the labels are correct; only the seed depth needs extending.


M1 PASS @2026-06-11T21:00Z

Re-claim: build #477 LEVEL 5 PASS, ADV-mailu-01 fix applied, both volumes (/data SQLite + /mail Maildir) now specifically tested.

Verdict: PASS — the fix correctly extends the backup/restore seed to cover both durable volumes. ADV-mailu-01 is closed.

What I verified (cold)

  1. PR#3 labels correct (branch add-backupbot-labels, head edc0201a79d36bc87696b0f93f1ee88ad7bd10ed):

    • admin service: backupbot.backup: "true" + backupbot.backup.path: "/data"
    • imap service: backupbot.backup: "true" + backupbot.backup.path: "/mail"
    • Version bump: 3.0.13.0.2+2024.06.52
  2. Build #477 evidence (Drone API + /var/lib/cc-ci-runs/477/results.json, cold read):

    • status: success, level: 5, all 5 rungs PASS ✓
    • clean_teardown: true, no_secret_leak: true
    • backup stage (all PASS):
      • test_backup_captures_mailbox PASS (1323ms) — SQLite /data
      • test_backup_captures_mail_message PASS (133ms) — Maildir /mail
    • restore stage (all PASS):
      • test_restore_returns_mailbox PASS (1359ms) — SQLite /data
      • test_restore_returns_mail_message PASS (189ms) — Maildir /mail
    • Clean teardown confirmed: docker stack ls on cc-ci shows no mailu-* stacks ✓
    • No mailu volumes leaked ✓
  3. Fix code review (commit b9352e8, cold):

    • ops.py::pre_backup: creates user + injects ccci-backup-probe message via sendmail in smtp container, polls doveadm search in imap container (≤60s) to confirm delivery ✓
    • ops.py::pre_restore: (1) deletes user from sqlite; (2) rm -rf /mail/{domain}/{localpart} in imap container — wipes maildir independently from sqlite record ✓
    • test_backup_captures_mail_message: doveadm search on imap asserts message present at backup time ✓
    • test_restore_returns_mail_message: same search after restore — asserts Maildir restored ✓
    • Both volumes exercised independently: pre_restore wipes each separately; restore must recover each ✓
  4. ADV-mailu-01 all three fix items satisfied:

    • (1) pre_backup injects a uniquely-tagged message via sendmail→dovecot deliver ✓
    • (2) pre_restore wipes the maildir (rm -rf /mail/{domain}/{localpart}) ✓
    • (3) test_restore asserts the message is back (doveadm search ≥1 result) ✓

ADV-mailu-01 closed — fix is real, CI proves it, no weakening of any assertion.

Builder is cleared to proceed to M2.


M2 PASS @2026-06-11T21:15Z

Claim: DEFERRED closed; levels reconciled; PARITY.md updated; operator summary written; fresh Adversary re-trigger via independent !testme on PR#3.

Verdict: PASS — all M2 DoD items verified independently. Phase mailu is DONE.

What I verified (cold)

  1. PR#3 still open, unmerged (Gitea API cold check):

    • state: open, head sha: edc0201a79d36bc87696b0f93f1ee88ad7bd10ed, merged: False ✓
  2. DEFERRED.md mailu entry closed:

    • Entry 2026-05-29 — mailu: no backup config marked [x] CLOSED @2026-06-11 with PR#3 + build #477 pointers; re-entry checkbox also ticked ✓
  3. PARITY.md updated with dual-volume evidence (tests/mailu/PARITY.md):

    • P4 section now states "earned via recipe-mirror PR#3" ✓
    • Documents both /data (SQLite) and /mail (Maildir) seeded + wiped + verified restored ✓
    • ops.py, test_backup.py, test_restore.py each described correctly ✓
    • Before/after level: backup_capable=False → L4-skipbackup_capable=True → L5-earned
  4. Levels reconciliation independently verified:

    • runner/harness/generic.py::backup_capable() scans compose*.yml for backupbot.backup.*true
    • Main branch: no backupbot labels → backup_capable=False → backup rung = intentional skip → L4
    • PR#3 head: admin+imap labels present → backup_capable=True → backup rung earned → L5
  5. Operator summary in STATUS-mailu.md: complete, accurate, actionable — specifies PR#3 URL, head SHA, what the PR adds, what CI proved, what operator must do (merge PR#3) ✓

  6. Fresh independent re-trigger (Adversary posted !testme on PR#3 at 2026-06-11T21:04:39Z, comment #14363):

    • Drone build #483: LEVEL 5 SUCCESS, recipe=mailu, PR=3, ref=edc0201a79d3
    • All 5 rungs PASS: install / upgrade / backup+restore / functional / lint ✓
    • Backup stage: test_backup_captures_mailbox PASS (1377ms) + test_backup_captures_mail_message PASS (149ms) ✓
    • Restore stage: test_restore_returns_mailbox PASS (1402ms) + test_restore_returns_mail_message PASS (168ms) ✓
    • clean_teardown: true, no_secret_leak: true
    • No mailu stacks or volumes on host post-run (docker stack ls + docker volume ls confirm) ✓
    • Result is reproducible: two independent builds (#477, #483) both LEVEL 5 at the same PR head ✓

Phase DoD satisfied

All items from plan-phase-mailu-backup.md §5:

  • Mirror PR open with evidence-justified backupbot v2 labels ✓ (PR#3)
  • backup→wipe→restore proven on real seeded mail data at PR head incl. drone path ✓ (builds #477 + #483)
  • mailu's backup rung earned (not skipped) with levels reconciled ✓
  • DEFERRED closed ✓
  • M1 + M2 fresh Adversary PASSes ✓ (this entry + M1 PASS above)
  • PR unmerged for the operator ✓

Phase mailu is complete. Builder is cleared to write ## DONE to STATUS-mailu.md.