Read-only recon (kb/audits/mail-sync-2026-08-06.md, OKF type: audit).
Stan wyjsciowy zmierzony na zywo: korpus gmail urywa sie 2026-06-19, dziura
48 dni ~ 1800 maili przy tempie ~37/dobe; zero kodu IMAP/JMAP w repo (tylko
dokumenty), brak modelu stanu synca — w bazie 3 tabele, zadnej z UID.
Ustalenia blokujace, ktore latwo przeoczyc (kazde zawodzi cicho, bez bledu):
- koperty bez entities[type=headers] daja prefiks chunka "(brak tematu) | ?"
(build_prefix), wiec poller musi pisac headers przy INSERCIE, nie backfillem
- DEFAULT_SUMMARYLESS_SOURCES = ("gmail",) — fastmail zembeduje sie i zniknie
z /search, bo galaz summaryless filtruje po source
- etap mailowy dopiety do kb-ingest.timer (03:30) zapali KbEmbedBacklogGrowing
na stale: SOLARIA wtedy spi (potwierdzone: kb_ingest_embed_skipped 1)
- envelope.id = goly Message-ID globalnie, wiec mail obecny na obu kontach
trafia do bazy raz, z source konta ktore wygralo wyscig
Architektura: fetch na PIHA co godzine (24/7, archiwum kanoniczne, bez GPU),
indeksowanie osobno bramkowane probe'em Ollamy (embed z PIHA zmierzony:
HTTP 200 w 8 ms, ~60 chunkow/dobe — rsync na SOLARIE zbedny). Spoiwem jest
kolejka "koperty bez chunkow", nie --since (ts to naglowek nadawcy).
Decyzje operatora (a)-(g) z rekomendacjami. Dwie korekty zalozen:
- POSTGRES_PASSWORD NIE lezy plaintextem w repo — service.yaml wymienia tylko
nazwy zmiennych, env.example ma placeholdery, skan sledzonych YAML: 0 trafien.
Rekomendacja uzywa istniejacego /opt/homelab/kb/.env (root:root 600,
czytany przez systemd przed zrzuceniem uprawnien)
- Fastmail przez IMAP, nie JMAP — domyka otwarta od czerwca decyzje
"unifikacja adaptera" (kb-mail-pillar.md §9); wymaga korekty §2/§7 tamtego
dokumentu po zatwierdzeniu
Zaleznosci z reconem multiagentowym: zadnych blokujacych. Dyspozytor to
subsystem B (osobny projekt); jawna zaleznosc to wiki-kompilat
(kb-m5-faza3.md:620 — "pelna wiki po przyrostowce").
Aktualizacja kb-m5-faza-mailowa.md: Krok 7 = WYKONANE + wskaznik do reconu.
Nic nie zaimplementowano, nie zdeployowano ani nie pobrano.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Warunek zdjecia M1 brzmial "R1 (prune filtrowany po restart policy / labelu
compose) wdrozony na tym nodzie". Zweryfikowane bezposrednio w kodzie dzialajacych
kontenerow, nie po datach deployu:
SOLARIA md5(/app/src/node_agent.py) = c9ac64e10b42b3e0ed9e4c168579bfaa,
identyczny z origin/master.
VPS rozni sie od origin/master wylacznie trescia komentarzy (5 linii
`docs/backlog.md` vs `kb/phases/backlog.md`, skutek migracji sciezek
w 9128530) — zero roznic funkcjonalnych.
Na obu nodach `_prune_stopped_containers` jest obecny (te same numery linii:
635/700/717/725), a jedyne wystapienia `containers.prune()` to tekst docstringa
i komentarza — brak wykonywalnego niefiltrowanego prune. Sprawdzone dodatkowo,
ze scripts/monitor/health-monitor.sh (ktory nadal ma niefiltrowane
`docker container prune -f` — R1 objelo tylko node_agent.py) nie jest wpiety w
zaden crontab ani timer na SOLARII i VPS, wiec node-agent byl faktycznie jedynym
zrodlem prune i cleanup byl na obu nodach realnie wylaczony.
SOLARIA: przywrocone jawne NODE_TYPE=ai_node — stan sprzed M1 (1cd6401),
zgodnie z konwencja pozostalych hostow, ktore wszystkie ustawiaja NODE_TYPE
jawnie (piha/lustro sd_card, chelsty-infra lte_node). solaria jest w AI_NODES,
wiec default dalby to samo, ale jawny wpis nie zalezy od hostname'u.
VPS: linia usunieta w calosci wraz z komentarzem TEMPORARY — dokladny stan
sprzed 11f3f80, gdzie NODE_TYPE nie bylo ustawione wcale. Potwierdzone, ze
default daje `standard`, nie None: base compose przekazuje `NODE_TYPE=${NODE_TYPE:-}`,
czyli pusty string, ktory jest falsy, wiec _resolve_node_type() schodzi do
rozpoznania po nazwie, a `vps` nie nalezy do LTE_NODES/SD_CARD_NODES/AI_NODES.
Sprawdzone na zlozonym `docker compose config` (NODE_TYPE: "") i uruchomieniem
_resolve_node_type() -> 'standard'. Rotacja filesystemu control-plane jest
bramkowana node_name == VPS_NODE_NAME, nie node_type, wiec dziala niezaleznie.
UWAGA DO DEPLOYU: na obu nodach brak /opt/homelab/state/last-docker-cleanup,
a przy braku markera _cleanup_rate_ok() zwraca True — pierwszy cleanup pojdzie
w pierwszym cyklu po restarcie (<=60 s), nie po 24 h jak na LUSTRO.
W chwili sprawdzenia zero kontenerow `exited` na obu nodach, wiec galaz
kontenerowa nie ma czego usunac; do sprzatniecia sa 4 dangling images na SOLARII
(~553 MB) i 1 na VPS (395 MB) plus build cache. humanai-mailer i humanai-landing
maja restart=unless-stopped, wiec sa chronione pierwsza galezia filtra R1 nawet
gdyby zostaly zatrzymane.
node-agent: 70 passed.
Refs docs/incidents/2026-07-30-ollama-solaria-vanish.md (§7, M1),
docs/sessions/2026-08-06.md (follow-up #5)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Wyciek plikow dispatch potwierdzony 2026-08-06 (session log, follow-up #1):
LUSTRO re-pullowalo te same dwie akcje co 60 s przez wiele dni, odbijajac sie
od bramki idempotencji, i nie zostawilo po sobie ani jednej linii w logach.
Przyczyna zlozona z dwoch niezaleznych defektow:
1. Executor tworzyl actions/dispatch/<node>/ z 0o755 (aerbot:aerbot). Rsync-pull
z noda uwierzytelnia sie jako inny uzytkownik, bedacy tylko *czlonkiem* tej
grupy. --remove-source-files musi zrobic unlink pliku, a unlink wymaga prawa
zapisu w katalogu nadrzednym, nie na samym pliku. Zrodlo przezywalo pobranie.
(dispatch/piha mialo historycznie 775 i dlatego dzialalo.)
2. node-agent traktowal rc=23 jako benign obok 0 i 24, wiec rsync zglaszal
porazke, a agent ja polykal.
Executor: _ensure_inbox_dir() = mkdir + bezwarunkowy os.chmod(0o775). chmod jest
bezwarunkowy z dwoch powodow: mkdir(mode=) jest maskowany przez umask procesu
(przy 0o022 daje dokladnie feralne 0o755), a inboxy zalozone przez wczesniejszy
build juz istnieja na flocie z 0o755. Naprawa w miejscu zapisu, a nie skanem przy
starcie: jedno idempotentne wywolanie na tej samej sciezce kodu, ktora pisze plik
dispatch, wiec nie da sie rozjechac z pisarzami. Blad chmod nie jest fatalny —
akcja i tak sie wykonuje, a nieskasowane zrodlo widac teraz po stronie noda.
Objete tez actions/deploy/<node>/ (deploy-runner): ten sam wzorzec drenowania
tym samym rsync-pullem, ten sam defekt, jedno wywolanie obok.
node-agent: klasyfikacja kodow wyjscia zamiast wspolnej listy benign.
Weryfikacja empiryczna rsync 3.4.1 pokazala, ze rc=23 pokrywa dwa rozne
przypadki, a rozroznia je dopiero stderr:
* `change_dir ... No such file or directory` — executor zaklada inbox dopiero
przy pierwszym dispatchu, wiec kazdy nod, do ktorego nic nie poszlo, dostaje
rc=23 co cykl. DEBUG — inaczej byloby po linii na minute z wiekszosci floty
i realny sygnal utonalby w szumie.
* `sender failed to remove <plik>: Permission denied` — wlasnie ten wyciek.
WARNING z pelnym stderr.
Pusty (ale istniejacy) inbox to rc=0, nie 23 — dotychczasowy komentarz w kodzie
mowil inaczej. rc=24 zostaje benign (wyscig z executorem piszacym inbox),
pozostale kody to teraz ERROR, nie WARNING. Zachowanie funkcjonalne bez zmian:
retry i idempotencja dzialaja jak dotad, zmienia sie wylacznie widocznosc.
Testy: 4 nowe w test_executor_dispatch.py (oba inboxy 0o775 pod umask 0o022,
naprawa istniejacego 0o755 in place, dispatch przezywa nieudany chmod), 5 w
test_action_dispatch.py na klasyfikacje rc. Zastapiony
test_pull_treats_empty_source_returncodes_as_non_error — kodyfikowal wlasnie to
zalozenie, ktore okazalo sie bugiem. Oba zestawy sprawdzone mutacja: bez chmod
padaja 3 testy executora, przy starej liscie benign pada test rc=23.
node-agent 70 passed, control-plane 173 passed.
Refs docs/sessions/2026-08-06.md (follow-up #1)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 13:42:47 +02:00
8 changed files with 1068 additions and 41 deletions
architektoniczna zakazująca stawiania na SOLARII czegokolwiek, co wymaga 24/7).
**Do rozstrzygnięcia razem z tym: takt indeksowania wobec dobowego cyklu
SOLARII** (§3.3). Rekomendacja: pobieranie co godzinę (`kb-mail-sync`, bez GPU),
indeksowanie częściej niż raz na dobę i bramkowane probe'em Ollamy, żeby
któryś tick trafił w okno pracy desktopu. Bez tego backlog embedów rośnie
każdej nocy i `KbEmbedBacklogGrowing` zapala się na stałe.
### (e) Zakres folderów per konto
**Rekomendacja:**
| Konto | Foldery | Uzasadnienie |
|---|---|---|
| gmail | **wyłącznie `\All`** (SPECIAL-USE, nie nazwa) | jedna wiadomość = jeden fetch mimo wielu etykiet; ta sama populacja co Takeout, na którym stoi 225 030 istniejących kopert → ciągłość korpusu |
| fastmail | `INBOX` + `Archive` + `Sent` | brak odpowiednika `\All`; te trzy pokrywają korespondencję prowadzoną i zarchiwizowaną |
Wyłączone z obu kont: `Spam`, `Trash`, `Drafts`. Spam i Trash to zdefiniowany
szum, a ich włączenie zmieniłoby populację względem tego, co już jest w bazie
(plan §Decyzja 4 odnotowuje, że Takeout „All Mail" nie zawiera Spamu). Drafty
nie są korespondencją — nie mają stabilnego Message-ID i mutują.
Zakres **musi być konfigurowalny listą per konto** (`MAIL_<ACCOUNT>_FOLDERS`),
bo tabela stanu jest kluczowana `(account, folder)`. Rozszerzenie o kolejny
folder jest wtedy zmianą konfiguracji, nie kodu, a dedup po Message-ID
gwarantuje, że dołożenie folderu z częściowo pokrywającą się zawartością niczego
nie zduplikuje.
**Osobne pytanie, którego nie rozstrzygam za operatora: historia Fastmaila.**
Konto ma zawartość sprzed dziś, a przyrostówka domyślnie zaczyna od
„od teraz" (`last_uid` = `UIDNEXT-1` przy pierwszym `SELECT`). Dwie opcje:
- **(e1) Tylko nowe.** Pierwszy tick zapisuje `last_uid` i nie pobiera nic
wstecz. Najprostsze, natychmiastowe, historia zostaje poza KB.
- **(e2) Pełny zaciąg.** Pierwszy przebieg z `UID SEARCH ALL` ściąga całą
zawartość wybranych folderów. Koszt zależy od rozmiaru skrzynki, którego
**nie znam** — to jest dokładnie ta „decyzja otwarta: sizing", którą
`kb-mail-pillar.md` §9 trzyma niezamkniętą od czerwca.
**Rekomendacja: (e2), ale dopiero po pomiarze.** Historia Fastmaila to
prawdopodobnie ta „sensowna poczta", o którą operatorowi chodziło bardziej niż
o gmailowe newslettery — a mechanizm jest ten sam co dla przyrostu, więc nie
kosztuje osobnego kodu. Warunek: najpierw jedno bezpieczne zapytanie
(`SELECT` folderu + `STATUS (MESSAGES)`), które poda liczbę wiadomości, i
dopiero na tej liczbie decyzja. Wykonalne w minutę **po** postawieniu klienta —
nie ma sensu zgadywać teraz.
### (f) Nowa decyzja, której zlecenie nie wymieniało: model stanu synca
Wypływa z §2.2 i wymaga zgody, bo dokłada **migrację `005`** do bazy, o której
faza mailowa deklarowała „zero migracji".
**Rekomendacja: tabela `mail_sync_state` w kb-postgres** (wariant A), nie plik
w `/opt/homelab/state/`. Powód rozstrzygający: stan synca i dane, którym
odpowiada, muszą się odtwarzać razem. Plik stanu przeżywający restore bazy
sprawia, że przyrostówka **cicho przeskakuje** wszystko między odtworzonym
stanem a bieżącym `last_uid` — awaria bez objawów, wykrywalna dopiero przy
zauważeniu brakujących maili miesiące później.
### (g) Nowa decyzja: `fastmail` w domyślnym trybie wyszukiwania
Z §2.5 (iii). `DEFAULT_SUMMARYLESS_SOURCES = ("gmail",)` sprawia, że koperty
fastmail byłyby niewidoczne w `/search` mimo poprawnego zembedowania.
**Rekomendacja: rozszerzyć do `("gmail", "fastmail")` w tym samym commicie,
który wprowadza źródło `fastmail`** — nie później. Rozdzielenie tych zmian tworzy
okno, w którym system wygląda na działający, a wyszukiwarka po cichu gubi całe
źródło. Zmiana jest jednowierszowa i nie ma wpływu na paperless (idzie kaskadą)
ani na inwariant startowy `kb-query` (sprawdza model embeddera, nie źródła).
---
## 5. Zależności z reconem multiagentowym i planem dyspozytora
| 7 | Recon przyrostówki IMAP/JMAP | — (po 6) | 1 sesja (poza DoD fazy) | **OTWARTE — NEXT** | Odblokowane przez zamknięcie Etapu B. Brak `jobs/fastmail-poller` / `jobs/gmail-imap-poller`, brak dokumentu reconu; IMAP/JMAP występuje wyłącznie jako zarys w §10 i w `kb-00-overview.md` |
| 7 | Recon przyrostówki IMAP/JMAP | — (po 6) | 1 sesja (poza DoD fazy) | **WYKONANE** (2026-08-06) | `kb/audits/mail-sync-2026-08-06.md`. Ustalenia: korpus urywa się 2026-06-19 (dziura 48 dni ≈ 1 800 maili), zero kodu IMAP w repo, brak modelu stanu synca; cały nowy kod to jeden `jobs/mail-imap-sync` + 4 drobne zmiany w istniejącym torze. Do decyzji operatora: (a)-(g) w §4 tamtego dokumentu |
**Kryterium ukończenia fazy mailowej:** (a) pełny korpus gmail zchunkowany
(bilans domknięty, `parse_errors` na poziomie pojedynczych sztuk jak