diff --git a/BACKLOG-mailu.md b/BACKLOG-mailu.md index 0de5886..c2b0111 100644 --- a/BACKLOG-mailu.md +++ b/BACKLOG-mailu.md @@ -4,4 +4,29 @@ (Builder-owned — read only for Adversary) ## Adversary findings -(Adversary-owned — no items yet; populated as issues are found) + +### [ADV-mailu-01] `/mail` Maildir volume restoration not tested — seed too shallow [adversary] + +**Filed**: 2026-06-11T20:58Z +**Status**: OPEN — blocks M1 + +**Plan requirement** (`plan-phase-mailu-backup.md` §2.3): "a seeded mailbox + message that survives +backup→wipe→restore — extend the existing functional helpers if the current seed is too shallow" + +**Repro**: +1. Current `ops.py::pre_backup` creates user account in SQLite (account record in `/data`), but never + injects a mail message into the Maildir at `/mail`. +2. `ops.py::pre_restore` deletes the SQLite account record only — does NOT wipe any maildir content. +3. `test_restore.py::test_restore_returns_mailbox` only asserts the account is back in config-export. +4. Result: the entire test exercises ONLY the `/data` (SQLite) volume; `/mail` (Maildir) restoration + is never specifically verified. If backupbot silently failed to restore `/mail`, this test passes. + +**Fix**: +1. `pre_backup`: inject a uniquely-tagged message into `citest@` mailbox via in-container + postfix→dovecot delivery (same mechanism as `test_mail_flow.py::test_send_and_receive_mail`) +2. `pre_restore`: additionally wipe the `citest@` maildir + (`doveadm expunge -u citest@ mailbox INBOX ALL` in the `imap` container) +3. `test_restore.py`: also assert the seeded message is back + (e.g., `doveadm search -u citest@ mailbox INBOX ALL` returns ≥1 result) + +**Only the Adversary closes this** after re-test with a fresh green build. diff --git a/REVIEW-mailu.md b/REVIEW-mailu.md index be9fa6d..f7d0ee5 100644 --- a/REVIEW-mailu.md +++ b/REVIEW-mailu.md @@ -22,3 +22,70 @@ Pre-phase independent research notes: - 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.1` → `3.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@` in config-export at backup time ✓ + - `test_restore_returns_mailbox` PASS — `citest@` 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@` 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@` from SQLite via sqlite3 — only wipes the DB record; + the Maildir at `/mail` is untouched throughout +- `test_restore.py`: asserts `citest@` 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@`, 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@` (e.g., + `doveadm expunge -u citest@ 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@ 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.