Plany faz KB (modul 5 fazy 2/3/4/mailowa, moduly 0/2/3/4, documents-ingest, raport fallback-dedup, eval retrieval-pilot) oraz prometheus-cutover-etap2, subsystem-a-naprawa, okit-cloudflare. git mv + frontmatter, tresc nietknieta. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
13 KiB
| okf | type | visibility | status | updated | links |
|---|---|---|---|---|---|
| 0.1 | phase | private | active | 2026-07-30 |
Raport dedup: fallback embed SOLARIA→PIHA — e7625cd (master) vs 3d4ee38 (task/kb-f4-fallback)
Data: 2026-07-30 · Worktree: task/kb-fallback-dedup · Status: salvage wykonany
2026-07-30 — operator zatwierdził pełną listę S1–S4 (sekcja „Rekomendacja zbiorcza");
zrealizowane na tym branchu: S1 (cherry-pick --transport http + README), S2 (session log
2026-07-27 z dopiskiem redakcyjnym), S3 (testy T1/T2/T3 w test_embed_router.py; T4
pominięty zgodnie z raportem), S4 (wynik kalibracji w override + README ollama-piha).
Weryfikacja: pytest kb-query 42/42 PASS; retrieval_eval.py --transport http przepuszczony
end-to-end przeciwko stubowi /search (raport + werdykt bramki generują się poprawnie;
kryterium 4 na stubie nie przechodzi wyłącznie dlatego, że mail_queries w queries.yaml
mają expected_envelope: null — placeholdery, artefakt danych, nie kodu).
Dwie równoległe sesje zaimplementowały ten sam krok planu (moduł 5 faza 4, §2 decyzja 2 / §5):
e7625cd(2026-07-29, ZMERGOWANY do master, wdrożony na PIHA):app/embed_router.pytest_embed_router.py+services/ollama-piha+ README z testami A/B/C.
3d4ee38(2026-07-27,origin/task/kb-f4-fallback, NIEZMERGOWANY):app/fallback.pytest_fallback.py+ własnyservices/ollama-piha+retrieval_eval.py --transport http- session log z kalibracją live.
Master = źródło prawdy. 3d4ee38 nie będzie mergowany — ten raport klasyfikuje jego deltę
i rekomenduje, co uratować.
1. Porównanie maszyn stanów (embed_router.py vs fallback.py)
Werdykt: embed_router.py na masterze pokrywa 100% zachowań fallback.py i dodaje
kilka istotnych rzeczy ponad to. Nic nie zgubił.
| Zachowanie (wymóg planu §2 D2 / zadania) | fallback.py (branch) |
embed_router.py (master) |
|---|---|---|
Health-check cache 30 s (probe GET /api/tags po wygaśnięciu) |
✅ SolCircuitBreaker, TTL 30 s |
✅ TTL 30 s (env EMBED_HEALTH_TTL_S) |
| Timeout probe'a | 0.5 s (stała) | 1.5 s (env; udokumentowane: absorbuje jitter Tailscale, spec zadania mówił „1–2 s") |
| Twardy timeout 3 s na nodze SOLARIA | ✅ ClientTimeout(total=3) przez nowy param embed_chunk(timeout_s=...) |
✅ asyncio.wait_for(3 s) wokół niezmienionego embed_chunk — bez ruszania pakietu kb-retrieval; obejmuje też parsowanie odpowiedzi |
| One-shot switch (fail mid-embed → ten sam request z PIHA, breaker „down" na okno TTL) | ✅ | ✅ + łapie dodatkowo ValueError (brak embedding w odpowiedzi) i loguje circuit open for 30s |
| Noga PIHA bez twardego timeoutu (cold-load, ostatnia deska) | ✅ | ✅ (celowe, udokumentowane w docstringu) |
| Powrót na SOLARIĘ ≤ 30 s po wygaśnięciu TTL | ✅ | ✅ |
| Obsługa 503 (oba backendy padnięte → 503, nie gołe 500) | ⚠️ częściowa: tylko aiohttp.ClientError → 503 w main.py; TimeoutError z nogi PIHA wyleciałby jako gołe 500 |
✅ dedykowany EmbedBackendError → 503; osobno pokryty przypadek „fallback nieskonfigurowany" |
Inwariant embed-model (query model == document_chunk.model) |
startowy DB-check + jedna stała embed_model na obie nogi (dowód strukturalny w teście) |
to samo plus leniwa weryfikacja per-backend przy pierwszym użyciu: /api/tags backendu musi listować EMBED_MODEL, inaczej ModelMismatchError → głośne 500 (nigdy cichy embed w złej przestrzeni wektorowej) |
/healthz dzieli cache z /search |
✅ sol_status |
✅ sol_status + dodatkowo fallback_status (up/down/unconfigured) |
embed_backend w odpowiedzi /search |
❌ (tylko sol_status) |
✅ (potrzebne do debugowania jakości per backend) |
| Praca bez fallbacku (env nieustawiony) | placeholder http://localhost:11434 (fail-closed, ale mylący sol_status) |
jawny tryb EMBED_FALLBACK_URL unset → zachowanie sprzed Kroku 2 (503) |
| Parametryzacja (TTL/timeouty) | stałe modułowe | env vars z defaultami |
| Logging decyzji routingu | ❌ | ✅ (kb-query.embed, backend=…, elapsed_ms) |
Różnice czysto kosmetyczne: semantyka wygaśnięcia dokładnie na granicy TTL identyczna
(obie strony: t == TTL → wygasłe); branch trzymał breaker jako dataclass z injektowalnym
zegarem, master trzyma to samo wewnątrz EmbedRouter (też injektowalny zegar).
2. Pełna delta 3d4ee38 (git diff origin/master...origin/task/kb-f4-fallback) + klasyfikacja
| Plik | Rozmiar delty | Klasyfikacja | Rekomendacja | Uzasadnienie |
|---|---|---|---|---|
services/kb-query/app/fallback.py |
+113 | duplikat funkcji | porzuć | embed_router.py = nadzbiór (tabela §1) |
services/kb-query/app/main.py |
±32 | duplikat | porzuć | master ma własną, pełniejszą integrację (mapowanie 503/500, env-schemat EMBED_PRIMARY/FALLBACK_URL) |
services/kb-query/app/search.py |
±36 | duplikat | porzuć | identyczny pomysł (embed raz → *_retrieve z gotowym wektorem); master dodaje embed_backend |
services/kb-query/tests/test_fallback.py |
+221 | duplikat + 3 unikalne scenariusze | adaptuj | luki testowe do przeniesienia do test_embed_router.py — patrz §3 |
services/kb-query/tests/test_search.py |
±38 | duplikat | porzuć | master zaadaptował te same testy przez _FakeRouter (czystsze — nie dubluje maszyny stanów w testach searcha) |
services/kb-query/{README,env.example,service.yaml,docker-compose.yml} |
~±58 | duplikat | porzuć | inny (porzucony) schemat env OLLAMA_PIHA_URL; master bogatszy (testy A/B/C, rename OLLAMA_URL→EMBED_PRIMARY_URL) |
packages/kb-retrieval/src/kb_retrieval/embed.py (timeout_s w embed_chunk) |
+16 | duplikat funkcji | porzuć | master osiąga twardy timeout przez asyncio.wait_for bez zmiany współdzielonego pakietu — mniejsza powierzchnia zmian, ten sam efekt |
jobs/documents-ingest/eval/retrieval_eval.py (--transport {direct,http} + --base-url) |
+109 | unikalna wartość | cherry-pick | plan §2 decyzja 6 / §9 (bramka HTTP-equivalence) — na masterze w ogóle nie istnieje; e7625cd nie tknął tego pliku, patch aplikuje się czysto; kod woła tylko GET /search i czyta envelope_id/dist/source — w pełni zgodny z odpowiedzią mastera |
jobs/documents-ingest/README.md |
+6 | unikalna wartość | cherry-pick | dokumentacja powyższego, idzie w parze |
docs/sessions/2026-07-27-kb-f4-fallback.md |
+189 | unikalna wartość | adaptuj | jedyny zapis: (1) znalezisko osieroconego natywnego ollama.service na PIHA + jego wyłączenie 2026-07-27 i backlog odinstalowania, (2) kalibracja live ollama-piha (GO: peak ~983 MiB, ~4.2–5.3 s/embed), (3) metodologia i wyniki bramki §9 (HTTP-equivalence 0 rozbieżności; sol-down Δ~3e-4), (4) rsync-deploy → dirty working tree na PIHA. Wciągnąć z dopiskiem redakcyjnym, że zmergowana implementacja to inny kod (e7625cd) i wyniki bramki wymagają powtórki |
services/ollama-piha/* (5 plików) |
+155 | duplikat | porzuć | wersja mastera lepsza: named volume ollama_piha_models (uzasadnienie uid-pattern PIHA), healthcheck sprawdza obecność bge-m3, bind tylko 127.0.0.1+LAN |
hosts/piha/runtime/ollama-piha/docker-compose.override.yml |
+13 | duplikat + 1 unikalny fakt | adaptuj (mikro) | ten sam mem_limit: 2560m; ale komentarz brancha zawiera potwierdzony pomiar (peak ~983 MiB), a master wciąż mówi „Confirm/trim after live calibration" — dopisać wynik kalibracji do komentarza override'u i/lub sekcji „Calibration" w services/ollama-piha/README.md |
hosts/piha/services.yaml |
±26 | duplikat | porzuć | master ma własny wpis ollama-piha + soft-dependency kb-query; drobna różnica (offline_required: true na branchu vs false na masterze) — master źródłem prawdy |
3. Pokrycie testowe — test_embed_router.py (13 testów) vs test_fallback.py (16 testów)
Scenariusze wspólne (pokryte po obu stronach): embed na SOLARIA gdy up; cache werdyktu
w TTL (bez re-probe); down z probe'a → PIHA bez próby embedu na SOLARIA; down cache'owany
(kolejne requesty omijają SOLARIĘ bez probe'a); one-shot same-request switch (wariant
connection-error); powrót na SOLARIĘ ≤30 s (test C — po wygaśnięciu TTL); oba
backendy padnięte → wyjątek; obie nogi z identycznym embed_model (na masterze mocniej:
weryfikacja /api/tags per backend).
Master ma ponadto testy, których branch nie miał: ModelMismatchError (fallback bez
bge-m3 → głośny błąd, nie cichy embed; primary bez bge-m3 → failover), brak fallbacku
skonfigurowanego → EmbedBackendError, fallback_status (up/unconfigured), sufiks
:latest spełnia gołą nazwę modelu.
Luki mastera względem test_fallback.py (kandydaci na salvage — pozycja „adaptuj" z §2):
| # | Scenariusz z test_fallback.py |
Stan na masterze | Waga |
|---|---|---|---|
| T1 | One-shot switch przy timeoutcie mid-embed (nie tylko connection-error) — na masterze to inna ścieżka kodu (asyncio.wait_for → TimeoutError) i jest nieprzetestowana |
brak | wysoka — to główny scenariusz produkcyjny („SOLARIA wisi", nie „SOLARIA odrzuca") |
| T2 | Po one-shot switchu breaker zostaje „down": kolejny request w tym samym oknie TTL idzie prosto na PIHA bez probe'a i bez próby SOLARIA | brak (master testuje cache „down" tylko z probe'a, nie z mid-embed failure) | średnia |
| T3 | Noga PIHA bez twardego timeoutu (strukturalna asercja) | brak (własność tylko udokumentowana) | niska — opcjonalnie |
| T4 | Wygaśnięcie cache dokładnie na granicy TTL (t == 30 s) |
brak (master testuje 31 s) | niska — semantyka i tak identyczna, można pominąć |
4. Rekomendacja zbiorcza (lista do zatwierdzenia)
- S1 — cherry-pick:
retrieval_eval.py --transport http --base-url+ akapit wjobs/documents-ingest/README.md(plan §2 D6/§9; aplikuje się czysto, zero zależności od porzuconego kodu brancha). - S2 — adaptuj:
docs/sessions/2026-07-27-kb-f4-fallback.md→docs/sessions/z dopiskiem redakcyjnym na górze (implementacja z tej sesji porzucona na rzecz e7625cd; fakty operacyjne — ollama.service, kalibracja, metodologia bramki — pozostają w mocy). - S3 — adaptuj: luki testowe T1 + T2 (T3 opcjonalnie) do
test_embed_router.py. - S4 — adaptuj (mikro): wynik kalibracji 2026-07-27 (peak ~983 MiB, ~4.2–5.3 s,
werdykt GO) do komentarza
hosts/piha/runtime/ollama-piha/docker-compose.override.ymli sekcji Calibration wservices/ollama-piha/README.md— pomiar dotyczył kontenera ollama-piha (ta sama konfiguracja: obraz,OLLAMA_KEEP_ALIVE=0,mem_limit 2560m), więc przenosi się na wersję mastera; różni się tylko storage (bind vs named volume), co nie wpływa na RAM/latencję. - Porzuć całą resztę delty (kolumna „porzuć" w §2).
5. Follow-up (po salvage)
- (a) Sprzątanie brancha (wykona Oskar): skasować
origin/task/kb-f4-fallback(3d4ee38) i worktree~/homelab-codex-ws-kb-f4-fallbackpo zakończeniu salvage. - (b) Powtórka testu sol-down na żywym masterze: kalibracja i bramka z 2026-07-27
dotyczyły innego kodu (
fallback.py, envOLLAMA_PIHA_URL) — na wdrożonyme7625cdtrzeba przejść testy A/B/C zservices/kb-query/README.mdoraz bramkęretrieval_eval.py --transport http(po S1): HTTP-equivalence przy SOLARIA-up (identycznedist) i sol-down (Δ≤epsilon, kolejność top-k identyczna; baseline z 27.07: Δ~3e-4). Symulacja wg README:EMBED_PRIMARY_URL=http://192.0.2.1:11434(TEST-NET), nie dotykając SOLARII. - (c) Weryfikacja stanu PIHA po podwójnym deployu (znalezisko z session loga):
sesja 27.07 wdrożyła kod brancha przez rsync do
~/homelab-codex-wsna PIHA (dirty working tree), a ollama-piha wystartował tam z bind mountem/opt/homelab/data/ollama-piha; master definiuje named volumeollama_piha_models. Sprawdzić: (1) working tree na PIHA czysty po merge'ue7625cdi kontenery faktycznie zbudowane z kodu mastera, (2) który storage żywy kontener naprawdę montuje; jeśli named volume — czybge-m3jest w nim spullowany, a stary katalog bind (/opt/homelab/data/ollama-piha, ~1.2 GB modelu) do skasowania, (3).envkb-query używaEMBED_FALLBACK_URL(schemat mastera), nie martwegoOLLAMA_PIHA_URL— szybki test:/healthzmusi zwracaćfallback_status: "up", nie"unconfigured". - (d) Backlog z 27.07 (przetrwa tylko dzięki S2): natywny, osierocony
ollama.servicena PIHA wyłączony (systemctl disable --now) 2026-07-27 — odinstalować binarkę/unit po ~2 tygodniach ciszy (≈ 2026-08-10).