homelab-codex-ws/docs/sessions/2026-06-24-kb-gmail-importer.md
oskar 4658089e21 fix(kb): przepiecie wszystkich odwolan wewnetrznych po migracji
126 plikow (md, yaml, sh, py) odwolywalo sie do sciezek sprzed migracji.

  15  markdown-linkow [..](..) -> policzona sciezka WZGLEDNA wobec pliku
      odsylajacego (wczesniej czesc z nich byla repo-root-relative i nie
      rozwiazywala sie z katalogu, w ktorym lezala)
 200  odwolan tekstowych (backticki, proza, yaml, importy w kodzie)
      -> nowa sciezka repo-root-relative, zgodnie z konwencja repo
   5  linkow rodzenstwa (gole nazwy plikow, np. "](DEPLOY.md)") — dzialaly
      tylko w starym katalogu; przeliczone recznie

Objete m.in.: CLAUDE.md (scripts/onboard/README.md -> kb/runbooks/
node-onboarding-tool.md, docs/backlog.md -> kb/phases/backlog.md),
README.md, .claude/skills/, 20 session logow, kod jobow.

Ostatnie 5 odwolan pochodzi z tresci wciagnietej rebasem z origin/master
(session log 2026-07-31, override node-agenta na SOLARII, dwie pozycje
backlogu) — wskazywaly na docs/incidents/, docs/kb/modules/ i
services/narty27/README.md sprzed migracji.

Dodany wzajemny link miedzy kb/services/control-plane.md (stub kodu)
a kb/subsystems/control-plane.md (opis, deprecated) — dwa dokumenty o tym
samym systemie, latwe do pomylenia.

Weryfikacja na 790 plikach: 0 odwolan do starych sciezek,
0 martwych linkow markdown. Lint OKF: 190/190 plikow ZGODNE.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 16:58:46 +02:00

178 lines
5.9 KiB
Markdown

---
okf: "0.1"
type: session-log
visibility: private
status: active
updated: 2026-06-24
links: []
---
# Sesja 2026-06-24 — KB etap 2: importer Gmail (kod gotowy)
## Cel
Zbudowanie jednorazowego bulk importera Gmail Takeout (`mbox → archiwum .eml + envelope DB`).
Import nie uruchomiony w tej sesji — Takeout na SOLARIA, transfer+wlew = następny krok.
---
## Stan infrastruktury po sesji
### kb-postgres na PIHA (korekta z etapu 1)
Baza **przeniesiona na PIHA**, nie na SOLARIA jak zakładał pierwotny plan.
- `Memory: hard=1g` zaaplikowany po reboocie (wymagał `cgroup_enable=memory` w cmdline + `docker-compose up --force-recreate`).
- Status: `healthy`, port `5433`.
### Google Takeout (Gmail „All Mail")
- Pobrany: **~15 GB zip**, po rozpakowaniu **~27 GB mbox**, jeden plik.
- Lokalizacja: `SOLARIA ~/Downloads` — jeszcze nie rozpakowany, nie zaimportowany.
---
## Etap 2 — ZBUDOWANY (kod gotowy)
### Nowa konwencja: `jobs/<name>/`
Jednorazowe i periodyczne joby (nie serwisy) żyją w `jobs/<name>/`.
| Katalog | Co tu trafia |
|---------|-------------|
| `packages/<lib>/` | reużywalne biblioteki Python (nie deployowane samodzielnie) |
| `services/<svc>/` | długo żyjące serwisy Docker |
| `jobs/<job>/` | one-shot i periodyczne joby CLI |
Instalacja lokalnie: `pip install -e packages/kb-mail/ && pip install -e jobs/gmail-bulk-import/`
Bez Dockera — odpalany bezpośrednio na PIHA pod `nice`/`ionice`.
### jobs/gmail-bulk-import
Wejście: plik `.mbox` z Google Takeout.
Wyjście: `.eml` w archiwum (append-only, PIHA NVMe) + wiersze `envelope` w kb-postgres.
Kluczowe decyzje implementacyjne:
| Kwestia | Decyzja |
|---------|---------|
| ID wiadomości | `Message-ID` header (stripped `<>`); fallback: `sha256-<32hex>` treści |
| Timestamp | `Date` header → UTC; fallback epoch 1970-01-01 + licznik `epoch_fallback` |
| Źródło | `source=gmail` |
| Idempotencja | `FileExistsError` z archiwum → skip; `ON CONFLICT DO NOTHING` w DB |
| Wznawialność | mbox iterowany od początku; już zarchiwizowane = skip; already in DB = noop |
| Batch inserty | `executemany` co 500 wpisów (+ flush na końcu); `_eml_ref` rekonstruuje `raw_ref` dla skipped |
| Docker | **brak** — CLI bez konteneryzacji |
### Załączniki → entities[]
W tym etapie bajty załączników **zostają w .eml** — nie są ekstrahowane ani OCR-owane.
Importer zapisuje **manifest** do `Envelope.entities[]`:
```json
{
"type": "attachment",
"filename": "faktura.pdf",
"content_type": "application/pdf",
"size": 42387,
"sha256": "a3f4..."
}
```
Powiązanie mail↔załącznik w bazie od dnia zero.
Ekstrakcja/OCR = **faza 2** (możliwe przekierowanie faktur/umów do Paperless, filar dokumentów).
### CLI
```bash
# Dry run — tylko liczenie i parsowanie, bez zapisów:
gmail-bulk-import --mbox ~/takeout/allmail.mbox --archive /data/kb/archive --dry-run
# Próbka 200 wiadomości (przed pełnym wlewem):
gmail-bulk-import --mbox ~/takeout/allmail.mbox --archive /data/kb/archive \
--dsn postgresql://kb:<pw>@localhost:5433/kb --limit 200
# Pełny wlew na PIHA pod nice/ionice:
ionice -c 3 nice -n 19 gmail-bulk-import \
--mbox ~/takeout/allmail.mbox \
--archive /data/kb/archive \
--dsn postgresql://kb:<pw>@localhost:5433/kb
```
### Statystyki zwracane przez importer
```
processed, imported, skipped, errors,
epoch_fallback, ← maile bez parsowalnej daty
msgs_with_attachments, ← sizing fazy 2
total_attachments,
total_attachment_bytes
```
### Testy
- 24 testy jednostkowe (bez DB, bez sieci) — wszystkie zielone.
- Pokrycie: `_message_id`, `_parse_date`, `_parse_attachments`, `run_import`
(dry-run, archiwum, idempotencja, --limit, epoch_fallback, statystyki załączników, batch).
---
## GOTCHA tej sesji
Pierwsza iteracja importera (commit 57a27af) celowała w `solaria:5433` zamiast PIHA,
pominęła `entities[]` załączników, `--limit`, batch inserty i `epoch_fallback`.
Złapane w review, poprawione w osobnym commicie.
**Lekcja**: prompt musi explicite podać host bazy = PIHA (nie SOLARIA).
---
## Następny krok
1. `rsync` Takeout SOLARIA → PIHA po Tailscale (27 GB mbox na NVMe PIHA).
2. `gmail-bulk-import ... --dry-run` — weryfikacja liczby wiadomości.
3. `gmail-bulk-import ... --limit 200` — próbka, weryfikacja jakości.
4. Pełny wlew pod `ionice -c 3 nice -n 19`.
5. Weryfikacja: `ls archive/gmail/ | wc``SELECT count(*) FROM envelope WHERE source='gmail'` ≈ liczba wiadomości w mboxie.
---
## Backlog (osobne zadania)
- `packages/kb-mail/tests/test_db.py`: `KB_TEST_DSN` defaultuje do `localhost:5433/kb` — poprawne dla PIHA, ale warto sprawdzić wszystkie occurrences `solaria:5433` w testach.
- Etap 3: Fastmail JMAP live ingest → `jobs/fastmail-poller/`.
- Etap 4: Gmail IMAP live sync → `jobs/gmail-imap-poller/`.
- Zapis deklaratywny `cgroup_enable=memory + swapaccount=1` dla PIHA (firmware/cmdline — poza GitOps, udokumentować w `hosts/piha/host.yaml` lub README).
---
## Higiena git
- Praca w worktree `task/kb-gmail-import`; master deploy-only, czysty przez cały czas.
- 4 commity na branchu po zamknięciu sesji.
---
## Commits
```
57a27af feat(kb-mail): etap 2 — jednorazowy bulk importer Gmail (mbox → archiwum)
f0e4d90 refactor(kb-mail): importer Gmail — entities załączników, --limit, batch, bez Dockera, DSN→PIHA
c5dd8f3 fix(piha): capabilities — realny RAM/NVMe + gitignore build dirs
<docs> docs(kb): sesja 2026-06-24 — importer Gmail gotowy + konwencja jobs/
```
## Files changed (etap 2)
```
jobs/gmail-bulk-import/src/gmail_bulk_import/__init__.py
jobs/gmail-bulk-import/src/gmail_bulk_import/importer.py
jobs/gmail-bulk-import/pyproject.toml
jobs/gmail-bulk-import/tests/test_importer.py
hosts/piha/capabilities.yaml
.gitignore
kb/subsystems/kb-overview.md (etap 2 gotowy, konwencja jobs/)
kb/subsystems/kb-mail-pillar.md (§8 krok 2 = kod gotowy)
docs/sessions/2026-06-24-kb-gmail-importer.md
```