353 lines
14 KiB
Markdown
353 lines
14 KiB
Markdown
|
|
---
|
|||
|
|
okf: "0.1"
|
|||
|
|
type: runbook
|
|||
|
|
visibility: private
|
|||
|
|
status: active
|
|||
|
|
updated: 2026-08-06
|
|||
|
|
links:
|
|||
|
|
- ../services/job-mail-imap-sync.md
|
|||
|
|
- ../audits/mail-sync-2026-08-06.md
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# mail-imap-sync — pierwsze uruchomienie i eksploatacja
|
|||
|
|
|
|||
|
|
Wszystko poniżej wykonuje **operator na PIHA**. Kod nie łączył się nigdy z żadnym żywym
|
|||
|
|
kontem — pierwszy sync jest nadzorowany i idzie krokami 1-9. Timer aktywujesz dopiero
|
|||
|
|
w kroku 9, po tym jak ręczny run przejdzie czysto.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 0. Punkty [do weryfikacji na żywo]
|
|||
|
|
|
|||
|
|
Recon (`kb/audits/mail-sync-2026-08-06.md`) oznaczył cztery twierdzenia o zewnętrznych
|
|||
|
|
dostawcach jako **niesprawdzone** — stan wiedzy modelu sięga maja 2026 i **nie jest
|
|||
|
|
wiarygodnym źródłem dla polityki dostawcy z sierpnia 2026**. Sprawdź je przy okazji kroków
|
|||
|
|
1-3 i dopisz wynik tutaj. Nie zgaduj.
|
|||
|
|
|
|||
|
|
| # | Do sprawdzenia | Gdzie | Wynik |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| 1 | Czy Google nadal wydaje hasła aplikacji (i czy wymagają 2FA na koncie)? Gdyby metoda odpadła, przymusem staje się OAuth2 — reszta architektury się nie zmienia, wymianie podlega **wyłącznie** sposób uwierzytelnienia w `kb_mail.imap.ImapClient.connect`. | Krok 1 | *(wpisz)* |
|
|||
|
|
| 2 | Czy Fastmail pozwala wydać hasło aplikacji **o zakresie tylko-IMAP** (bez dostępu do panelu)? Jeśli tak, jest to uprawnienie ściślejsze niż token JMAP — i to domyka argument za unifikacją adaptera. | Krok 2 | *(wpisz)* |
|
|||
|
|
| 3 | Czy konto Fastmail wystawia jakiś folder wirtualny obejmujący całość (odpowiednik gmailowego `\All`)? Jeśli tak, zakres `INBOX+Archive+Sent` można uprościć. | Krok 4 (`--measure` wypisze wszystkie foldery po `LIST`) | *(wpisz)* |
|
|||
|
|
| 4 | Dobowy limit transferu IMAP Gmaila (rzędu kilku GB). Przy ~1 800 zaległych maili (~225 MB) i ~4,6 MB/dobę bieżąco nie ma znaczenia — ma, gdyby ktoś kiedyś ciągnął IMAP-em pełną historię. | Krok 6, jeśli pierwszy sweep się urwie | *(wpisz)* |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Hasło aplikacji — Gmail
|
|||
|
|
|
|||
|
|
Wymaga włączonego 2FA na koncie (bez tego Google nie pokaże opcji haseł aplikacji).
|
|||
|
|
Wygeneruj hasło aplikacji dla „Mail", zapisz je **od razu do menedżera haseł** — Google
|
|||
|
|
pokazuje je jeden raz.
|
|||
|
|
|
|||
|
|
> **Nie wypisuj hasła na ekran i nie wklejaj go do żadnej sesji.** W tym repo są dwa
|
|||
|
|
> udokumentowane przypadki rotacji sekretu po wycieku do transkryptu sesji
|
|||
|
|
> (`docs/sessions/2026-07-15.md` §Krok 3, `docs/sessions/2026-07-21.md` incydent 3). To jedyna
|
|||
|
|
> droga, którą sekrety w tym homelabie dotąd wyciekały.
|
|||
|
|
|
|||
|
|
Google wyświetla hasło w czterech grupach po cztery znaki — **wpisuj bez spacji**.
|
|||
|
|
|
|||
|
|
## 2. Hasło aplikacji — Fastmail
|
|||
|
|
|
|||
|
|
Wydaj hasło aplikacji o zakresie **IMAP** (nie „full access"), jeśli konto na to pozwala —
|
|||
|
|
to punkt (2) z tabeli wyżej. Zapisz tak samo jak wyżej.
|
|||
|
|
|
|||
|
|
## 3. Wypełnienie `.env`
|
|||
|
|
|
|||
|
|
Sekrety dopisujesz do **istniejącego** `/opt/homelab/kb/.env` (już `root:root 0600`, już
|
|||
|
|
trzyma `KB_DSN`, `PAPERLESS_API_TOKEN`, `ANTHROPIC_API_KEY`). Edytorem, nie `echo >>` —
|
|||
|
|
`echo` zostawia hasło w historii powłoki.
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
sudo -e /opt/homelab/kb/.env
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Wzorzec kluczy i komentarze: `jobs/mail-imap-sync/env.example`. Minimum:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
MAIL_ACCOUNTS=gmail,fastmail
|
|||
|
|
MAIL_GMAIL_USER=...
|
|||
|
|
MAIL_GMAIL_APP_PASSWORD=...
|
|||
|
|
MAIL_GMAIL_INITIAL_MODE=since
|
|||
|
|
MAIL_GMAIL_INITIAL_SINCE=2026-06-15
|
|||
|
|
MAIL_FASTMAIL_USER=...
|
|||
|
|
MAIL_FASTMAIL_APP_PASSWORD=...
|
|||
|
|
MAIL_FASTMAIL_INITIAL_MODE=new-only
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Sprawdź uprawnienia po edycji — muszą zostać `600 root:root`:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
sudo ls -l /opt/homelab/kb/.env
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Dlaczego akurat tak: systemd czyta `EnvironmentFile` **jako root, przed zrzuceniem uprawnień
|
|||
|
|
do `User=oskar`**. Sekret trafia do procesu, ale nie jest czytelny dla `oskar` w spoczynku.
|
|||
|
|
To lepsza własność niż plik `600 oskar:oskar` — nie „napraw" jej.
|
|||
|
|
|
|||
|
|
### Skąd data dla `MAIL_GMAIL_INITIAL_SINCE`
|
|||
|
|
|
|||
|
|
Kilka dni **przed** najnowszą kopertą w bazie — nakładka jest darmowa (dedup po Message-ID),
|
|||
|
|
a luka nie jest:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker exec kb-postgres psql -U kb -d kb -c \
|
|||
|
|
"SELECT source, count(*), max(ts) FROM envelope GROUP BY source ORDER BY source;"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Stan na 2026-08-06: gmail 225 030 kopert, `max(ts) = 2026-06-19 19:13:39+02`. Stąd
|
|||
|
|
`2026-06-15` w przykładzie wyżej. `SINCE` filtruje po INTERNALDATE serwera (kiedy wiadomość
|
|||
|
|
przyszła), nie po nagłówku `Date:` — czyli po właściwej stronie tego rozróżnienia.
|
|||
|
|
|
|||
|
|
## 4. Instalacja i migracja
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd ~/homelab-codex-ws && git pull
|
|||
|
|
source /opt/homelab/kb/venv/bin/activate
|
|||
|
|
pip install -e packages/kb-mail/ -e jobs/mail-imap-sync/
|
|||
|
|
# mail-body-ingest jest potrzebny etapowi mailowemu w kb-ingest (patrz krok 8):
|
|||
|
|
pip install -e packages/kb-retrieval/ -e jobs/mail-body-ingest/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Migracja `005` — `initdb` odpala skrypty z `init/` **tylko na świeżym wolumenie**, więc na
|
|||
|
|
istniejącej bazie aplikujesz ją ręcznie:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker exec -i kb-postgres psql -U kb -d kb \
|
|||
|
|
< ~/homelab-codex-ws/services/kb-postgres/init/005_mail_sync_state.sql
|
|||
|
|
|
|||
|
|
docker exec kb-postgres psql -U kb -d kb -c "\d mail_sync_state"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Oczekiwane: kolumny `account, folder, uidvalidity, last_uid, last_sync_ts` i
|
|||
|
|
`PRIMARY KEY (account, folder)`. DDL jest `IF NOT EXISTS` — powtórzenie jest bezpieczne.
|
|||
|
|
|
|||
|
|
## 5. Pomiar — ile waży Fastmail
|
|||
|
|
|
|||
|
|
**To jest moment, w którym zapada decyzja o historii Fastmaila.** Recon celowo jej nie
|
|||
|
|
podjął: zależy od liczby, której nikt jeszcze nie znał (Decyzja (e), „najpierw pomiar, potem
|
|||
|
|
decyzja"). Odłożona **do tego kroku**.
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
set -a; . /opt/homelab/kb/.env; set +a
|
|||
|
|
mail-imap-sync --measure
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Tryb jest read-only: `STATUS (MESSAGES UIDNEXT UIDVALIDITY)` per folder, zero zapisów, zero
|
|||
|
|
dotknięcia flag wiadomości. Weryfikuje przy okazji, że oba hasła aplikacji działają.
|
|||
|
|
|
|||
|
|
Odczytaj sumę `messages` dla `INBOX + Archive + Sent` i wybierz:
|
|||
|
|
|
|||
|
|
| Rozmiar | `MAIL_FASTMAIL_INITIAL_MODE` | Uwagi |
|
|||
|
|
|---|---|---|
|
|||
|
|
| kilka-kilkanaście tysięcy | `full` | Rzędu godzin transferu; mechanizm ten sam co przyrost, więc nie kosztuje osobnego kodu. To prawdopodobnie ta „sensowna poczta", o którą chodziło bardziej niż o gmailowe newslettery. |
|
|||
|
|
| dziesiątki tysięcy+ | `full` **z `--limit`** | Pierwszy sweep w kawałkach, np. `--limit 2000` na tick; kursor idzie od najstarszych, reszta dochodzi kolejnymi tickami. |
|
|||
|
|
| nieinteresująca | `new-only` | Historia zostaje poza KB. |
|
|||
|
|
|
|||
|
|
Wpisz wybór do `.env` **zanim** puścisz pierwszy `--apply` — po pierwszym runie istnieje
|
|||
|
|
wiersz w `mail_sync_state` i `INITIAL_MODE` przestaje mieć jakikolwiek wpływ.
|
|||
|
|
|
|||
|
|
Przy okazji: `--measure` po drodze robi `LIST`, więc log pokaże wszystkie foldery konta —
|
|||
|
|
stąd odpowiedź na punkt (3) z tabeli w §0.
|
|||
|
|
|
|||
|
|
## 6. Dry run, potem pierwszy sync
|
|||
|
|
|
|||
|
|
Dry run nie pobiera ani jednej wiadomości — robi EXAMINE + SEARCH i mówi, ile by ściągnął:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mail-imap-sync
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Czytaj `candidates` per folder. Dla gmaila przy `since=2026-06-15` spodziewaj się rzędu
|
|||
|
|
**~1 800** (48 dni × ~37/dobę). Rząd wielkości zupełnie inny niż oczekiwany = zatrzymaj się
|
|||
|
|
i sprawdź konfigurację, nie puszczaj `--apply`.
|
|||
|
|
|
|||
|
|
Pierwszy prawdziwy run — z hamulcem, do pliku, na niskim priorytecie:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mkdir -p /opt/homelab/logs/kb-mail-sync
|
|||
|
|
nice -n 10 ionice -c2 -n7 mail-imap-sync --apply --limit 200 \
|
|||
|
|
> /opt/homelab/logs/kb-mail-sync/first-run.log 2>&1
|
|||
|
|
echo "exit=$?"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Sprawdź w logu linię `summary`:
|
|||
|
|
|
|||
|
|
- `envelopes_inserted` > 0, `errors` = 0,
|
|||
|
|
- `archived` + `archive_exists` = `processed`,
|
|||
|
|
- `balance_problems` puste,
|
|||
|
|
- `headers_fallback` — pojedyncze sztuki są normalne (8-bitowe nagłówki), dziesiątki nie.
|
|||
|
|
|
|||
|
|
Potem puszczaj bez `--limit`, aż `candidates` w dry runie spadnie do zera:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mail-imap-sync --apply >> /opt/homelab/logs/kb-mail-sync/first-run.log 2>&1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Kontrola w bazie:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker exec kb-postgres psql -U kb -d kb -c \
|
|||
|
|
"SELECT account, folder, uidvalidity, last_uid, last_sync_ts FROM mail_sync_state ORDER BY 1,2;"
|
|||
|
|
docker exec kb-postgres psql -U kb -d kb -c \
|
|||
|
|
"SELECT source, count(*), max(ts) FROM envelope GROUP BY source ORDER BY 1;"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`max(ts)` dla gmaila powinien być z ostatnich dni, a `source='fastmail'` pojawić się po raz
|
|||
|
|
pierwszy w historii tej bazy.
|
|||
|
|
|
|||
|
|
## 7. Indeksowanie i weryfikacja end-to-end
|
|||
|
|
|
|||
|
|
Sam sync **nie chunkuje i nie embeduje** — celowo (recon §3.2). Nowe koperty czekają
|
|||
|
|
w kolejce, którą jest brak chunków. Zdrenuj ją ręcznie raz, przy włączonej SOLARII:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mail-body-ingest --only-unchunked --apply \
|
|||
|
|
>> /opt/homelab/logs/kb-mail-sync/first-ingest.log 2>&1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Pełny test od końca do końca — wyślij sobie maila z rozpoznawalną frazą, potem:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 1. sync go pobiera
|
|||
|
|
mail-imap-sync --apply
|
|||
|
|
|
|||
|
|
# 2. koperta jest w bazie
|
|||
|
|
docker exec kb-postgres psql -U kb -d kb -c \
|
|||
|
|
"SELECT id, source, ts FROM envelope ORDER BY ts DESC LIMIT 5;"
|
|||
|
|
|
|||
|
|
# 3. ingest robi chunki i embeddingi (SOLARIA musi być włączona)
|
|||
|
|
mail-body-ingest --only-unchunked --apply
|
|||
|
|
|
|||
|
|
# 4. widać go w wyszukiwarce, trybem domyślnym (hybrid)
|
|||
|
|
curl -s "http://localhost:8230/search?q=<fraza+z+maila>" | jq '.results[] | {envelope_id, dist}'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Krok 4 jest tym, co weryfikuje Decyzję (g): `fastmail` musi być w
|
|||
|
|
`DEFAULT_SUMMARYLESS_SOURCES`, inaczej koperty zaindeksują się poprawnie i **nie pojawią się
|
|||
|
|
w żadnym wyniku** — bez błędu gdziekolwiek. Jeśli mail z Fastmaila nie wychodzi w `/search`,
|
|||
|
|
a z gmaila wychodzi, to jest pierwsze miejsce do sprawdzenia.
|
|||
|
|
|
|||
|
|
## 8. Instalacja jednostek systemd (jeszcze bez włączenia)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
sudo install -m 0755 ~/homelab-codex-ws/jobs/mail-imap-sync/systemd/kb-mail-sync-run.sh \
|
|||
|
|
/opt/homelab/kb/kb-mail-sync-run.sh
|
|||
|
|
sudo cp ~/homelab-codex-ws/jobs/mail-imap-sync/systemd/kb-mail-sync.{service,timer} \
|
|||
|
|
/etc/systemd/system/
|
|||
|
|
sudo systemctl daemon-reload
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Równocześnie: zmiana taktu `kb-ingest`** (Decyzja (d), recon §3.3). Timer przechodzi
|
|||
|
|
z 03:30 raz na dobę na **co 2 h**, a `kb-ingest` dostaje etap mailowy:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
sudo cp ~/homelab-codex-ws/jobs/documents-ingest/systemd/kb-ingest.timer /etc/systemd/system/
|
|||
|
|
sudo systemctl daemon-reload && sudo systemctl restart kb-ingest.timer
|
|||
|
|
systemctl list-timers kb-ingest.timer
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Dlaczego to musi iść razem z uruchomieniem przyrostówki: o 03:30 SOLARIA prawie na pewno śpi
|
|||
|
|
(potwierdzone 2026-08-06 odczytem `kb_ingest_embed_skipped 1`). Dopięcie ~60 nowych chunków
|
|||
|
|
na dobę do ticku, który każdej nocy pomija embedowanie, sprawiłoby, że backlog rośnie
|
|||
|
|
monotonicznie i `KbEmbedBacklogGrowing` (próg `> 0` przez 72 h) **zapala się na stałe** — nie
|
|||
|
|
sygnalizując awarii, tylko rozjazd harmonogramu z dobowym cyklem SOLARII. Alert, który świeci
|
|||
|
|
zawsze, przestaje być alertem.
|
|||
|
|
|
|||
|
|
Reguła alertowa dla samego synca (`services/fleet-prometheus/rules/kb-mail-sync.yml`) wchodzi
|
|||
|
|
przy najbliższym deployu fleet-prometheus. Dopóki timer nie chodzi, metryka nie istnieje,
|
|||
|
|
`time() - <brak serii>` nie daje wyniku i reguła jest bezczynna — kolejność nie ma znaczenia.
|
|||
|
|
|
|||
|
|
## 9. Aktywacja timera
|
|||
|
|
|
|||
|
|
Dopiero teraz, i tylko jeśli kroki 6-7 przeszły czysto:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
sudo systemctl enable --now kb-mail-sync.timer
|
|||
|
|
systemctl list-timers kb-mail-sync.timer
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Po pierwszym automatycznym ticku:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
journalctl -u kb-mail-sync.service -n 50
|
|||
|
|
cat /opt/homelab/state/node-exporter/kb-mail-sync.prom
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
W `.prom` szukaj `kb_mail_sync_last_success_timestamp` (niezerowy) i
|
|||
|
|
`kb_mail_sync_envelopes_inserted{account="..."}`.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Eksploatacja
|
|||
|
|
|
|||
|
|
### Codzienne sprawdzenie
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
tail -n 20 /opt/homelab/logs/kb-mail-sync/run-$(date +%Y%m%d).log
|
|||
|
|
docker exec kb-postgres psql -U kb -d kb -c \
|
|||
|
|
"SELECT account, folder, last_uid, last_sync_ts FROM mail_sync_state ORDER BY 1,2;"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`last_sync_ts` ma się przesuwać co godzinę **nawet gdy nic nie przyszło** — to jest odpowiedź
|
|||
|
|
na pytanie „czy poller w ogóle działa", niezależna od tego, czy ktoś do nas napisał.
|
|||
|
|
|
|||
|
|
### Folder stanął — jedna wiadomość blokuje kursor
|
|||
|
|
|
|||
|
|
Objaw: `errors` niezerowe co tick, `last_uid` w `mail_sync_state` stoi, w logu ten sam UID
|
|||
|
|
przy `skip.fetch_error` / `skip.store_error`.
|
|||
|
|
|
|||
|
|
To jest zaprojektowane zachowanie: kursor nie przeskakuje wiadomości, której nie udało się
|
|||
|
|
zapisać (przeskakiwanie = ciche gubienie poczty). Wiadomości *po* niej i tak lądują
|
|||
|
|
w archiwum i w bazie — blokada dotyczy wyłącznie kursora.
|
|||
|
|
|
|||
|
|
Jeśli wiadomość jest trwale nie do pobrania, przestaw kursor ręcznie o jeden:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker exec kb-postgres psql -U kb -d kb -c \
|
|||
|
|
"UPDATE mail_sync_state SET last_uid = <zly_uid> WHERE account='gmail' AND folder='<folder>';"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Zanotuj w `kb/incidents/`, którego maila świadomie pominięto.
|
|||
|
|
|
|||
|
|
### Serwer unieważnił UIDVALIDITY
|
|||
|
|
|
|||
|
|
W logu `imap.uidvalidity_reset`, w metrykach `kb_mail_sync_uidvalidity_resets{account} = 1`.
|
|||
|
|
Job sam robi pełne przemiecenie folderu i opiera się na dedupie po Message-ID — nie ma tu nic
|
|||
|
|
do zrobienia ręcznie. Tick będzie dłuższy i `envelopes_skipped_dup` skoczy do rozmiaru
|
|||
|
|
folderu; to jest poprawny przebieg, nie awaria.
|
|||
|
|
|
|||
|
|
### Rotacja hasła aplikacji
|
|||
|
|
|
|||
|
|
Odwołaj stare u dostawcy, wydaj nowe, podmień w `/opt/homelab/kb/.env`, `systemctl start
|
|||
|
|
kb-mail-sync.service`. Restart timera nie jest potrzebny — `EnvironmentFile` czytany jest przy
|
|||
|
|
każdym starcie serwisu. Nic w stanie synca nie zależy od poświadczeń.
|
|||
|
|
|
|||
|
|
### Dołożenie folderu
|
|||
|
|
|
|||
|
|
Dopisz go do `MAIL_<KONTO>_FOLDERS` w `.env`. Nowy folder nie ma wiersza w `mail_sync_state`,
|
|||
|
|
więc obowiązuje go `INITIAL_MODE` tego konta — jeśli chcesz jego historię, przestaw na `full`
|
|||
|
|
**na jeden run**, potem wróć do poprzedniej wartości (istniejące foldery mają już kursory
|
|||
|
|
i zmiana ich nie dotyczy). Zawartość pokrywająca się z innym folderem niczego nie zduplikuje.
|
|||
|
|
|
|||
|
|
## Testy
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
pip install -e "jobs/mail-imap-sync[dev]"
|
|||
|
|
cd jobs/mail-imap-sync && pytest
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
80 testów, bez sieci i bez bazy: `ImapClient` jest napędzany podstawionym połączeniem
|
|||
|
|
`imaplib` (więc testowane jest prawdziwe parsowanie protokołu, nie mock adaptera), a
|
|||
|
|
`envelope`/`mail_sync_state` są w pamięci. Pokrycie: nowe wiadomości, uniewaznienie
|
|||
|
|
UIDVALIDITY, dedup na trzech warstwach, wznowienie po przerwaniu, kolizja Message-ID między
|
|||
|
|
kontami, izolacja kont, wygasłe UID-y, dry-run bez sieci, przenoszenie
|
|||
|
|
`last_success_timestamp` przez nieudany run.
|
|||
|
|
|
|||
|
|
W worktree bez gotowego venva:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
python3 -m venv /tmp/kbvenv && /tmp/kbvenv/bin/pip install -q pytest pytest-asyncio \
|
|||
|
|
-e packages/kb-mail/ -e jobs/mail-imap-sync/
|
|||
|
|
/tmp/kbvenv/bin/python -m pytest jobs/mail-imap-sync/tests packages/kb-mail/tests -q
|
|||
|
|
```
|