homelab-codex-ws/docs/sessions/2026-06-17-kb-foundations.md
oskar bbfbb698f8 docs(kb): etap 1 fundament done + session log 2026-06-17
- kb-00-overview.md: etap 1 oznaczony jako DONE (kb-postgres + koperta +
  packages/kb-mail), następny krok = etap 2 bulk Gmail; dodana sekcja
  konwencji packages/
- CLAUDE.md: sekcja "Shared Python Libraries (packages/)" — konwencja,
  layout, instalacja w Dockerfile
- docs/sessions/2026-06-17-kb-foundations.md: pełny log sesji (architektura
  KB, decyzje, etap 1 zbudowany, poprawki spójności, następny krok)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 20:02:25 +02:00

5.5 KiB
Raw Blame History

Sesja 2026-06-17 — KB foundations (etap 1 maili)

Cel

Zbudowanie fundamentu filaru maili: pgvector spine na SOLARIA, zamrożona koperta, pakiet domeny kb-mail, testy. Bez live ingestu, bez bulk Gmaila, bez indexera — sam fundament pod kolejne etapy.


Architektura KB — ustalona w tej sesji (docs)

4 warstwy × 4 filary + rdzeń

[ 4. Agent interdyscyplinarny ]   NL query + analityka (LLM)         ← wspólne
[ 3. Join + Enrich ]   entity resolution · graf · OwnTracks           ← wspólne
[ 2. Preprocess + Index ]   → pgvector / → SQL                        ← per filar
[ 1. Ingest ]   Maile  Dokumenty  Zdjęcia  Transakcje                 ← per filar

OwnTracks = klucz czasoprzestrzenny w warstwie 3, nie osobny filar (ciągła funkcja czas→miejsce).

Decyzje zamknięte

  • Spine: Postgres + pgvector (nie Qdrant) na SOLARIA
  • Embed: bge-m3 (multilingual, długi kontekst, lepszy pod polski)
  • Maile: dwa żywe źródła — Fastmail (JMAP, primary) + Gmail (IMAP; konto zostaje jako śmieciowe/2FA)
  • Bulk historyczny Gmail (jednorazowy) — etap 2
  • Reguła: archiwizuj wszystko, indeksuj selektywnie (filtr na wejściu do indeksu, nie archiwum)
  • Załączniki: II tura (MVP = czysty tekst + nagłówki)
  • Warstwa 3 startuje jako cienki graf encji; federacja przy zapytaniu — później
  • Dokumenty: Nextcloud (drive, WebDAV) + Paperless-ngx (skany/faktury/OCR)
  • Koperta: zamrożona addytywna — id / source / ts(UTC) / geo / raw_ref / entities[]

Decyzje otwarte

  • Transakcje (filar #4): OPEN ISSUE — agregator PSD2 (GoCardless / Tink) dla mBank + Revolut, ale zgoda SCA wygasa co 90 dni → brak pełnego bezobsługowego sync; fallback: CSV/MT940. Do decyzji przy starcie filaru #4.

Etap 1 — ZBUDOWANY

services/kb-postgres

  • Obraz: pgvector/pgvector:pg16
  • SOLARIA, port 5433 (5432 zarezerwowany na potencjalny lokalny postgres)
  • Named volume: kb_postgres_data
  • Init SQL (init/001_envelope.sql): CREATE EXTENSION vector + tabela envelope
  • Per-host override: hosts/solaria/runtime/kb-postgres/docker-compose.override.ymlmem_limit: 4g
  • inventory/topology.yaml + hosts/solaria/services.yaml — wpisy dodane

Zamrożona koperta (001_envelope.sql + Envelope dataclass)

CREATE TABLE envelope (
    id       TEXT PRIMARY KEY,
    source   TEXT NOT NULL,       -- fastmail | gmail | ...
    ts       TIMESTAMPTZ NOT NULL,
    geo      JSONB,               -- null dla maili; warstwa 3 uzupełnia
    raw_ref  TEXT NOT NULL,       -- ścieżka do .eml w archiwum
    entities JSONB NOT NULL DEFAULT '[]'
);

Envelope dataclass waliduje ts.tzinfo is not None w __post_init__.

packages/kb-mail — nowa konwencja shared lib

Lokalizacja: packages/<lib>/ (nie services/) — biblioteki reużywalne przez joby. Instalacja w Dockerfile: COPY packages/kb-mail/ /packages/kb-mail/ && pip install /packages/kb-mail/.

Moduł Co robi
envelope.py @dataclass Envelope, walidacja tz-aware ts
db.py insert_envelope (ON CONFLICT DO NOTHING) + get_envelope (asyncpg)
archive.py save_eml — append-only, asyncio.to_thread na write, FileExistsError na duplikat

structlog JSON w każdym module (structlog.get_logger(__name__)); konfiguracja po stronie konsumenta (biblioteka nie konfiguruje structlog).

Testy

  • 15 unit testów (nie-integration): model, archiwum, sanity SQL — wszystkie zielone bez DB
  • 5 integration testów (@pytest.mark.integration): round-trip DB, duplikat, geo, entities — wymagają KB_TEST_DSN
  • conftest.py: fixture db_conn = asyncpg connection + BEGIN/ROLLBACK na każdy test

Poprawki spójności (code-review sesji)

  1. README deploy: pierwotna wersja miała docker compose --env-file /opt/homelab/config/… — pomijała override file i miała złą ścieżkę. Naprawiono: README dokumentuje trzy ścieżki (standardowa przez deploy.sh solaria, first-time setup .env obok compose file, manual z dwoma -f), zgodnie z deploy-node.sh L6572.
  2. .gitignore: reguła *.env (L3) już łapie services/kb-postgres/.env — potwierdzono git check-ignore. Nic nie dodano.

Higiena git

  • Cała praca w worktree task/kb-foundations — master czysty przez cały czas
  • 2 commity na branchu po zakończeniu sesji

Commits

31c64d5 feat(kb-mail): fundament — pgvector spine, koperta, archiwum, pakiet domeny
<docs-commit> docs(kb): etap 1 fundament done + session log 2026-06-17

Files changed (etap 1)

services/kb-postgres/docker-compose.yml
services/kb-postgres/service.yaml
services/kb-postgres/env.example
services/kb-postgres/healthcheck.sh
services/kb-postgres/init/001_envelope.sql
services/kb-postgres/README.md
hosts/solaria/runtime/kb-postgres/docker-compose.override.yml
hosts/solaria/services.yaml
inventory/topology.yaml
packages/kb-mail/pyproject.toml
packages/kb-mail/src/kb_mail/{__init__,envelope,db,archive}.py
packages/kb-mail/tests/{conftest,test_envelope,test_archive,test_db,test_migration}.py
docs/kb/kb-00-overview.md   (etap 1 done, konwencja packages/)
CLAUDE.md                   (sekcja Shared Python Libraries)
docs/sessions/2026-06-17-kb-foundations.md

Następny krok

Etap 2: jednorazowy bulk importer Gmail — mbox/Takeout → archiwum. Wejście: plik .mbox lub katalog Maildir z eksportu Google Takeout. Wyjście: .eml w archiwum + wiersze envelope w DB. Jako one-shot job (nie serwis), reużywa packages/kb-mail.