Domkniecie Kroku 7. Realizuje Decyzje (d) reconu (host schedulera + korekta
kadencji indeksowania) i doklada dokumentacje wg konwencji OKF.
Scheduler (NIEAKTYWOWANY — wlacza operator):
- jobs/mail-imap-sync/systemd/{service,timer,run.sh} — wzorzec 1:1 z kb-ingest,
OnCalendar=hourly, Persistent=true, log do pliku (nigdy sam journal).
- hosts/piha/jobs.yaml — deklaracja jednostek host-level na PIHA. Nowy plik, bo
services.yaml jest dla kontenerow (supervisor dopasowuje jego wpisy do world-state
i wpis niekontenerowy dryfowalby wiecznie jako missing_service). Nic tego pliku
nie czyta — istnieje po to, zeby "shadow-deploy family" z otwartego pytania 5
reconu multiagentowego byla spisana, a nie tylko na nodzie.
Takt indeksowania (Decyzja (d), recon §3.3):
- kb-ingest.timer: 03:30 raz na dobe -> co 2 h. O 03:30 SOLARIA prawie na pewno spi
(potwierdzone odczytem kb_ingest_embed_skipped 1 z 2026-08-06), a tick dostaje
teraz etap mailowy: ~60 nowych chunkow na dobe pomijanych kazdej nocy sprawiloby,
ze backlog rosnie monotonicznie i KbEmbedBacklogGrowing zapala sie NA STALE.
Co 2 h zamiast stalej godziny — probe Ollamy sam wybiera okno, wiec ktorys tick
w nie trafi niezaleznie od nawykow operatora.
- cyclic_ingest: etap mailowy (mail_body_ingest --only-unchunked), import miekki,
wiec venv bez tego pakietu pomija etap zamiast wywracac wrapper. Predykat bledu
JEST luzniejszy niz wlasne main() tamtego joba i to jedyne takie miejsce w tym
wrapperze: pojedynczy trwale nieparsowalny mail nie moze zamrozic
last_success_timestamp i zapalic KbIngestStale na zawsze. Bledy per-mail sa
publikowane jako kb_ingest_mail_parse_errors, nie chowane.
Obserwowalnosc: KbMailSyncStale (6 h bez udanego ticku). Alert na cisze w skrzynce
ODRZUCONY (decyzja operatora, zgodna z reconem §3.4) — zero nowych maili to legalny
stan skrzynki, a alert zapalajacy sie na zdrowym systemie zostaje wyciszony
i przestaje dzialac wtedy, gdy jest potrzebny.
Dokumentacja:
- kb/services/job-mail-imap-sync.md (OKF), kb/runbooks/mail-sync-run.md — 9 krokow
pierwszego uruchomienia, w tym checklista 4 punktow [do weryfikacji na zywo]
z reconu (polityki dostawcow — do sprawdzenia, nie do zgadniecia) oraz pomiar
STATUS (MESSAGES) na Fastmailu, na ktorym zapada ODLOZONA decyzja o historii.
- kb-mail-pillar.md: KOREKTA JMAP -> IMAP dla Fastmaila jako decyzja 2026-08-06;
stary zapis zostaje jako historia z data. Zamkniete "unifikacja adaptera"
i "sizing Gmaila"; otwarte zostaje "sizing Fastmaila" — celowo, bo rozstrzyga
je pomiar, nie dyskusja.
- kb-m5-faza-mailowa.md: Krok 7 IN PROGRESS + tabela zakresu wdrozonego,
kb-m5-faza3.md: korekta harmonogramu i sekwencji wrappera,
pkg-kb-mail.md: rozpisany ze stubu, kb-postgres.md: lista migracji + 005.
Testy: 642 passed (calosc kb-mail, kb-retrieval i jobs). systemd-analyze verify
na timerze przechodzi, OnCalendar=0/2:00:00 normalizuje sie do co 2 h.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3.2 KiB
| okf | type | visibility | status | updated | links | |||
|---|---|---|---|---|---|---|---|---|
| 0.1 | service | private | active | 2026-08-06 |
|
kb-mail
Shared library for the mail pillar. Everything that more than one mail job needs lives here, by rule rather than by taste: the corpus rests on 225 030 envelopes whose ids, archive paths and header entities were produced by one implementation, and a second copy of any of those derivations would silently stop agreeing with the first.
Kod: packages/kb-mail/ (src/kb_mail/, pyproject.toml, tests/)
Dependencies: asyncpg, structlog. Nothing else — the IMAP transport is stdlib imaplib.
Modules
| Module | What it holds | Used by |
|---|---|---|
envelope |
The frozen cross-source Envelope dataclass (mirrors envelope in kb-postgres). Additive contract. |
everything |
db |
asyncpg helpers: insert_envelope (returns the command tag), get_envelope, rows_affected, envelope_source |
all jobs |
archive |
save_eml — append-only .eml writer, {root}/{source}/{YYYY}/{MM}/{id}.eml. FileExistsError is the "already have it" signal, never masked. |
bulk-import, imap-sync |
text |
sanitize_surrogates, strip_nul — both mandatory before anything reaches text/jsonb |
all parsing paths |
chunking |
chunk_text (2400/600 chars), extracted in Krok 0 |
mail-body-ingest, documents-ingest |
headers |
parse_headers, parse_headers_fallback, parse_headers_resilient — the entities[type=headers] shape, typed parse with a compat32 fallback |
header-backfill, imap-sync |
message |
message_id, parse_date, parse_attachments, eml_ref, EPOCH — envelope id, timestamp, attachment manifest, archive path |
bulk-import, imap-sync |
imap |
ImapAccount, ImapClient, FolderStatus — read-only IMAP over stdlib imaplib |
imap-sync |
sync_state |
mail_sync_state accessors + plan_folder_sync / contiguous_last_uid |
imap-sync |
headers and message were extracted from gmail-header-backfill and gmail-bulk-import
respectively when mail-imap-sync needed the same derivations (2026-08-06). Both jobs
re-export them under their original names, so their CLIs and test suites are unchanged. This
is the same move as chunking in Krok 0 — extract, do not copy.
Why the IMAP transport lives here and not in the job
The adapter is protocol code, and the sync-cursor semantics (UIDVALIDITY invalidation, how far
a cursor may advance) are protocol semantics — both are worth testing in isolation, and both
would otherwise be buried in a job's loop where a wrong branch is invisible. kb_mail.imap and
kb_mail.sync_state therefore carry the rules; jobs/mail-imap-sync carries the orchestration
and the policy. See kb/services/job-mail-imap-sync.md for the behaviour they add up to.
Tests
pip install -e "packages/kb-mail[dev]"
cd packages/kb-mail && pytest
111 unit tests, no DB or network. tests/test_db.py is additionally marked integration and
needs a live kb-postgres (KB_TEST_DSN, default localhost:5433) — it is the only file that
does, and it errors rather than skips when the DB is absent.