--- okf: "0.1" type: audit visibility: private status: active updated: 2026-08-26 as_of: 2026-08-26 links: - ../phases/kb-m5-faza3.md - ../phases/kb-m5-faza-mailowa.md - ../services/narty27.md - ../services/kb-query.md - ../services/kb-site.md - ../subsystems/kb-mail-pillar.md - ../subsystems/kb-overview.md - ../subsystems/kb-documents-pillar.md - ../subsystems/recon-multiagent.md - ../decisions/architektura-2026-07-28.md - ../decisions/backlog-aktywne.md - ../incidents/2026-08-26-mail-sync-20-dni-ciszy.md - ../audits/mail-sync-2026-08-06.md - ../runbooks/mail-sync-run.md - ../services/job-mail-imap-sync.md - ../services/pkg-kb-retrieval.md --- # Recon — wiki-kompilat (faza 5), 2026-08-26 Read-only recon. Zakres: przygotowanie fazy 5 subsystemu B (KB) — syntezy wiki nad warstwą dowodową RAG/pgvector. Zlecenie: zbadać 9 punktów (lokalizacja, bootstrap encji, tryb kompilatora, format linku dowodowego, anty-propagacja, integracja z kb-query, zależności/kolizje, plan iteracji, decyzje operatora) i wynieść wniosek per punkt. **Nic nie zostało zaimplementowane, zdeployowane ani skommitowane poza tym dokumentem.** Dowody: repo (worktree `task/wiki-kompilat-recon`, cięty z mastera @ `4fa10f0`), pilot `~/narty-2027/saalbach-kb` na SOLARII (lokalny odczyt), żywa baza `kb` na PIHA przez `ssh piha 'docker exec kb-postgres psql ...'` (SELECT-y, zero zapisów), `systemctl status` na PIHA dla stanu timerów. **Znalezisko nadrzędne, zanim cokolwiek innego:** to nie jest recon od zera. `kb/phases/kb-m5-faza3.md` §8 zawiera **już zatwierdzony przez operatora szkic architektoniczny** wiki-kompilatu (decyzja 2026-07-15, opisana jako „NIE podlega zmianie") plus rozwinięcie wykonawcze (§8.2, 2026-07-17) z konkretnymi rozstrzygnięciami: osobne repo `kb-wiki`, struktura katalogów, format frontmattera z blokiem `sources`, inline-przypisy `[^envelope_id#chunk_id]`, trzypoziomowa kaskada retrievalu `wiki → summary → chunk`, kompilacja przez CC/API, lint okresowy, pętla zwrotna. Zadanie tego reconu — tak jak je czytam — to **zweryfikować te ustalenia w świetle tego, co się wydarzyło od 07-17** (pilot narty27, cała faza mailowa, incydent 20 dni ciszy), dociągnąć luki, których tamten szkic nie rozstrzygał (chunk-id vs re-chunking, self-citation, konkretna lista encji z prawdziwych danych), i złożyć to w plan pierwszego etapu. Poniżej cytuję zamiast odtwarzać, i mówię wprost, gdzie się zgadzam, a gdzie proponuję dociągnięcie. --- ## 1. Stan korpusu na dziś (2026-08-26) — czy blokada z faza3 jest zdjęta `kb/phases/kb-m5-faza3.md:628`, cytat ze szkicu operatora: *„Pełna wiki po fazie mailowej (przyrostówka) — wcześniej kompilat byłby fotografią przeszłości."* `kb/audits/mail-sync-2026-08-06.md` §7 nazywa to *jedyną znalezioną jawną zależność „X czeka na przyrostówkę" w całym KB*. Zmierzone dziś na żywej bazie: ```sql SELECT source, max(ts)::date AS latest, count(*) FROM envelope GROUP BY 1; ``` | source | latest | count | |---|---|---| | gmail | **2026-08-26** | 227 280 | | fastmail | **2026-08-26** | 105 | | paperless | 2026-07-13 | 191 | ```sql SELECT count(*) FROM document_chunk WHERE embedding IS NULL AND excluded_reason IS NULL; -- → 0 ``` Korpus mailowy sięga **dziś**, nie 2026-06-19 jak w audycie 08-06 — incydent [20 dni ciszy](../incidents/2026-08-26-mail-sync-20-dni-ciszy.md) naprawiony tego samego dnia co to zlecenie domknął przyrostówkę: kursor dogonił IMAP, 548 zaległych kopert wchłonięte, zero błędów. `kb-mail-sync.timer` (co godzinę) i `kb-ingest.timer` (co 2h, embed) oba `active (waiting)` na PIHA, żywe od 2026-08-06, bez przerwy odkąd monitoring realnie działa (od tego samego incydentu). Embed backlog = **0** — nic nie czeka na wektor. **Wniosek: blokada z faza3 jest zdjęta dziś, nie wcześniej.** To jest właściwy moment na ten recon — zrobiony tydzień wcześniej trafiłby na korpus wciąż urwany, a wiki-kompilat skompilowałby na trwałe fotografię z czerwca. Warto odnotować kruchość tego stanu: przyrostówka działa dzięki jednemu `chown` i jednemu `/-/reload`, oba ręczne, oba bez automatyzacji (R1/R2 z incydentu — poprawna własność katalogów przy `sudo`-runach i reload reguł Prometheusa w deployu — zostają otwarte). Faza 5 dziedziczy to ryzyko: gdyby przyrostówka znów ucichła cicho, wiki-kompilat kompilowałby dalej z coraz starszej wody, bez sygnału. Patrz §7 niżej (interakcja z monitoringiem `KbMailSyncStale`). **191 kopert paperless** nie rosną od 2026-07-13 — to osobny fakt: paperless nie ma przyrostówki (dokumenty trafiają tam ręcznie), niezwiązany z blokadą mailową. Nie wstrzymuje fazy 5. --- ## 2. Punkt 1 — Lokalizacja wiki: `kb/wiki/` w repo vs osobne repo ### Co już zdecydowano `kb-m5-faza3.md` §8.2: *„Repozytorium (rozstrzygnięcie punktu 6 szkicu): osobne repo `kb-wiki` — decyzja 7, uzasadnienie w §2. Klon roboczy na PIHA: `/opt/homelab/data/kb-wiki/`."* — i dalej: *„D7 — rekomendacja: osobne `kb-wiki`"* w podsumowaniu (§12). To jest już podjęta decyzja, nie propozycja. ### Weryfikacja na wzorcu pilota narty27 (`~/narty-2027/saalbach-kb`) **potwierdza ten wzorzec empirycznie**: `kb/services/narty27.md` — *„Content is personal and lives outside the repo… It is never committed — not to this repo, not to any other."* Źródło leży w osobnym lokalnym gicie na SOLARII, poza `homelab-codex`, infra (`services/narty27`) zostaje w `homelab-codex`. Dokładnie ten sam split, jaki faza3 projektuje dla `kb-wiki`: **treść osobno, infrastruktura hostująca w repo.** ### Rekomendacja: potwierdzam D7 (osobne repo), z jednym doprecyzowaniem Zgadzam się z decyzją bez zmian co do kierunku. Powody, które faza3 już podała i które podtrzymuję: 1. **Cykl życia inny niż kodu infry.** Commity do `kb-wiki` będą częste, drobne, generowane maszynowo (`compile: `) — zalałyby historię `homelab-codex-ws`, gdzie commituje operator ręcznie i rzadko (`CLAUDE.md`: *„Primary control node — only node where commits are made"*). 2. **Widoczność `visibility` per strona nie mapuje się na model `homelab-codex`.** `check_okf.py` tego repo ma zamkniętą listę `TYPES` i regułę `POINTER_GLOBS`/ `EXCLUDE_DIRS` dopasowaną do tego, co tu jest — dodanie setek stron-encji (firmy, osoby, sprawy) do `kb/` zaszumiłoby indeks tego repo i pomieszałoby dwa różne reżimy publikacji (kb-site publikuje `visibility: public` z `kb/`; `kb-wiki` potrzebuje **własnego**, bo część stron — dane osobowe, umowy, ludzie — nigdy nie powinna nawet teoretycznie wpaść w tę samą ścieżkę co dokumentacja infrastruktury). 3. **Rozmiar.** Korpus mailowy to 227k+ kopert; nawet skromna kompilacja (setki stron) to inny rząd wielkości commitów niż `homelab-codex-ws` widział kiedykolwiek. Osobne repo izoluje ten wzrost od repo, które operator przegląda i na którym pracują agenty infrastrukturalne. **Doprecyzowanie względem faza3:** szkic nie rozstrzyga *dostępu CC w worktree*. `homelab-codex-ws` ma wzorzec `agent.sh new` → worktree per task, każdy dostaje pełne repo `homelab-codex-ws` sklonowane z gita. `kb-wiki` **nie jest** tym repo — sesja kompilująca stronę potrzebuje **dwóch** klonów jednocześnie: `homelab-codex-ws` (żeby czytać `packages/kb-retrieval`, DSN, `.env` na PIHA) i `kb-wiki` (żeby pisać stronę). To nie jest problem dla worktree pojedynczej sesji CC odpalonej ręcznie na PIHA/SOLARII z dwoma katalogami obok siebie — **jest** problemem dla wzorca `scripts/dev/agent.sh new`, który zna tylko jedno repo. Rekomendacja: kompilacja wiki **nie** idzie przez `agent.sh`-owe worktree `homelab-codex-ws` — to osobny tryb pracy (sesja CC z dwoma katalogami roboczymi, jak dziś narty27 na SOLARII), do zapisania w `kb-wiki/_meta/conventions.md`, nie w tym repo. ### Publish.sh i public/private per strona `kb-site` (`services/kb-site/`) publikuje wyłącznie `kb/**/*.md` z `visibility: public`, fail-closed, przez token w URL (`kb-e2a24af3.okit.pl`). `kb-wiki` leżący poza `kb/` **nie wejdzie** do tego pipeline'u automatycznie — i to dobrze, bo strony wiki (dane o ludziach, umowach, finansach) mają zupełnie inny profil ryzyka publikacji niż dokumentacja infrastruktury. Jeśli/kiedy pojawi się potrzeba publicznej (lub choćby LAN-owej) wystawki `kb-wiki`, to osobny generator + osobny serwis (wzorzec `narty27`: nginx + named volume, `exposure: private` domyślnie), **nie** rozszerzenie `gen_pages.py`. Na tym etapie (proof-of-concept, 3–10 stron) nie ma potrzeby stawiać hostingu w ogóle — `git log`/`cat` na PIHA wystarczy do przeglądu. --- ## 3. Punkt 2 — Bootstrap: analiza korpusu i lista encji zalążkowych ### Zapytania i wyniki (żywa baza, 2026-08-26) **Wolumen per rok/źródło** (`envelope`, pełne dane w §1 wyżej dla świeżości; poniżej rozkład historyczny): ```sql SELECT source, date_trunc('year', ts) AS yr, count(*) FROM envelope GROUP BY 1,2 ORDER BY 1,2; ``` Szczyt 2011–2012 (~23k/rok — dawny pracodawcy przez `outbox.pl`), potem stabilne 9–13k/rok 2013–2025. 2 559 kopert `ts`=epoch 1970 (daty nieodzyskane, opisane w `kb-overview.md` backlog). **Top nadawcy wśród kopert, które realnie mają zaembedowany, nie-wykluczony chunk** (czyli faktycznie trafiają w wyniki `/search` — to jest właściwe sito pod encje, nie surowy wolumen, który zdominowany jest przez newslettery wykluczone z indeksu): ```sql SELECT h->'from'->>'name', h->'from'->>'address', count(DISTINCT e.id), min(e.ts)::date, max(e.ts)::date FROM envelope e JOIN jsonb_array_elements(e.entities) h ON h->>'type' = 'headers' WHERE EXISTS (SELECT 1 FROM document_chunk c WHERE c.envelope_id = e.id AND c.embedding IS NOT NULL AND c.excluded_reason IS NULL) GROUP BY 1,2 ORDER BY 3 DESC LIMIT 40; ``` | Nadawca | Adres | Koperty | Zakres | |---|---|---|---| | oskar kapala | oskar.kapala@gmail.com | 10 021 | 2005–2020 | | Oskar Kapala | oskar.kapala@outbox.pl | 6 399 | (epoch)–2014 | | Kasia Lorenc-Kapala | katalia@gmail.com | 3 385 | 2007–2026 | | Oskar Kapala | oskar.kapala@gmail.com | 2 910 | 2010–2026 | | System Synergia | robot2@robot.librus.pl | 2 818 | 2018–2026 | | Allegro | powiadomienia@allegro.pl | 2 170 | 2008–2026 | | Tomasz Anuszewski | tomasz.anuszewski@outbox.pl | 1 913 | 2011–2012 | | Dziennik Bankier.pl (×2 adresy) | …bankier.pl | 1 736 + 1 676 | 2007–2014 | | RSW_TECH_TEAM | rsw_tech_team@outbox.pl | 1 628 | 2011–2012 | | Groupon | info@news.groupon.pl | 1 447 | 2011–2012 | | Miron Mironiuk | m@cosmose.co | 1 126 | 2015–2016 | | kontakt@mbank.pl | | 1 099 | 2005–2026 | | Jan Boboli | jan.boboli@outbox.pl | 969 | 2011–2013 | | Paweł Cesar Sanjuan Szklarz | paweld2@gmail.com, pawel@cosmose.co | 757+555+418+376 | 2005–2024 | | InPost | info@paczkomaty.pl | 722 | 2017–2026 | | Bogumil Jakubiak | B.Jakubiak@icm.edu.pl | 686 | 2004–2009 | | sOKratis Liliana Banaszak | liliana.banaszak@sokratis.pl | 679 | 2009–2026 | | Strava | update.strava.com / strava.com | 591+395 | 2017–2026 | **Domeny** (§2, po odsianiu marketingu/newsletterów po heurystyce `noreply|notification|robot|mailer`): `outbox.pl` (39,4k — domena prywatna operatora, alias `oskar.kapala@outbox.pl` + koledzy z tej samej domeny = prawdopodobnie dawny pracodawca/hosting), `gmail.com`, `cosmose.co` (2 982, firma — Miron Mironiuk + Paweł Szklarz), `icm.edu.pl`/`students.mimuw.edu.pl` (uczelnia/ICM UW), `mbank.pl`/`bankier.pl` (finanse), `sokratis.pl`. **Paperless — 191 kopert, w większości streszczone** (`document_summary`, 162 `claude-haiku-4-5` + 155 `gemma3:12b`, prawie 1:1 pokrycie). Próbka streszczeń pokazuje wyraźne klastry tematyczne: | Klaster | Liczba (próbka 40 najnowszych) | Przykład | |---|---|---| | FIRST LEGO League / Fundacja Future Minds | 8 | zgody na wizerunek, karty wyników, harmonogramy | | Regulaminy OLX (pakiety, ochrona kupującego, ads) | 5 | umowa serwisowa | | Revolut (krypto, regulaminy, cennik) | 4 | umowa finansowa | | Spółdzielnia Mieszkaniowa „Przy Metrze" | 2 | RODO, dane kontaktowe | | Ubezpieczenia (PZU OC, itp.) | 2 | polisa | | Wynajem auta (Dollar, Madryt) | 2 | rezerwacja | | Faktura (ZigBee, Oskar Kapała Rozwiązania IT — działalność gospodarcza) | 1 | dowód zakupu | ### Kryterium kolejności Trzy sygnały, w tej kolejności ważności: 1. **Obecność w indeksowanym (nie-wykluczonym) korpusie** — encja musi mieć realne pokrycie w `document_chunk` z wektorem, inaczej strona kompiluje się z pustki. To jedyny twardy filtr (zastosowany w zapytaniu wyżej). 2. **Wielowątkowość źródeł** — encje, które łączą paperless (dokument formalny: umowa, polisa, regulamin) *i* gmail (korespondencja o tej samej sprawie), są lepszymi kandydatami na `sprawy`/`umowy` niż encje widoczne tylko w jednym źródle — bo demonstrują dokładnie to, co ma udowodnić wiki-kompilat (Karpathy-wzorzec: strona syntezuje, nie kopiuje jednego dokumentu). 3. **Rozstrzygalność, nie wolumen.** `outbox.pl`/`Tomasz Anuszewski`/`Jan Boboli`/`RSW_TECH_TEAM` mają duży wolumen (2011–2012), ale to zamknięty w czasie epizod (prawdopodobnie dawny pracodawca) — dobry kandydat na `podmiot`, zły na pierwszy PoC, bo wymaga ustalenia kontekstu (kim była ta firma), którego recon nie ma. Odłożone na drugą turę, nie na pierwszą. ### Proponowana lista 10–20 encji zalążkowych | # | Strona | Typ | Uzasadnienie (z danych) | |---|---|---|---| | 1 | `fll-2025-26` | sprawa | 8/40 streszczeń paperless; żywa sprawa (2026), krzyżuje szkołę + fundację + dziecko | | 2 | `future-minds` (fundacja) | podmiot | organizator FLL, NIP w danych, kontakt jawny w treści dokumentów | | 3 | `spoldzielnia-przy-metrze` | podmiot | nieruchomość, RODO + dane kontaktowe, aktywna (bieżąca korespondencja o pożarówce 2026) | | 4 | `revolut` | podmiot | 4 dokumenty regulaminowe w próbce, konto aktywne (kontekst kryptowalut) | | 5 | `mbank` | podmiot | 1 099 kopert 2005–2026, ciągłość 20 lat, kontakt@mbank.pl + dziennik bankier.pl (powiązana marka) | | 6 | `pzu` | podmiot | kandydat wymieniony wprost w szkicu operatora (§8.1 przykład), polisa OC w paperless | | 7 | `cosmose` (firma) | podmiot | 2 982 kopert, dwóch nazwanych korespondentów (Mironiuk, Szklarz) — test cross-osoba w jednym podmiocie | | 8 | `miron-mironiuk` | osoba | 1 126 kopert, jeden zidentyfikowany adres, powiązanie z `cosmose` | | 9 | `pawel-cesar-sanjuan-szklarz` | osoba | 4 warianty adresu/nazwy (757+555+418+376 kopert) — dobry test entity resolution na aliasach tej samej osoby | | 10 | `librus-synergia` | temat | 2 818 kopert 2018–2026, szkoła dzieci, ciągły strumień — test strony-tematu o dużym, jednorodnym źródle | | 11 | `inpost-paczkomaty` | temat | 722+681 (dwa nadawcy tej samej usługi) — test scalania dwóch adresów jednej usługi w jedną stronę | | 12 | `strava` | temat | 591+395, dwa adresy tej samej usługi, 2017–2026 ciągłe | | 13 | `bogumil-jakubiak` | osoba | ICM UW, 2004–2009 — zamknięty epizod, test strony `status: archived` | | 14 | `wynajem-samochodow` (Dollar/Madryt) | temat | rezerwacja 2026, dwa powiązane dokumenty paperless (potwierdzenie różnymi kanałami tej samej rezerwacji) — test dedupu treści | | 15 | `dzialalnosc-gospodarcza` (Oskar Kapała Rozwiązania IT) | podmiot | faktura w paperless, własna działalność — inny rejestr niż korespondencja prywatna | | 16 | `sokratis` | podmiot | 679 kopert 2009–2026, ciągłość 17 lat, nazwany kontakt (Liliana Banaszak) | | 17 | `allegro` | temat | 2 170 kopert 2008–2026 — najdłuższa ciągła relacja handlowa w korpusie | | 18–20 | (druga tura, po pierwszej weryfikacji) | — | `outbox.pl`/dawny pracodawca po ustaleniu kontekstu; `PZU`↔`mbank` cross-link jako pierwszy test grafu `sprawy` łączących dwa `podmioty` | 17 pozycji zamiast równych 20 — reszta świadomie zostawiona na drugą turę zamiast dopełniana sztucznie (Bogumil Jakubiak/ICM to jedyny archiwalny/ zamknięty przypadek na liście — reszta to żywe encje; warto mieć chociaż jeden przykład `status: archived` w PoC, żeby lint (inwariant 3) miał co sprawdzać na obu stanach od pierwszego dnia). --- ## 4. Punkt 3 — Tryb kompilatora: batch / pipeline / hybryda ### Co już zdecydowano Faza3 §8.2, inwariant 2: *„Kompilację i lint robi CC/zewnętrzne API… Sesja CC/API dostaje wyniki retrievalu dla encji… pisze/aktualizuje stronę, commituje do kb-wiki."* To opisuje **batch przez sesje CC** — potwierdzone też przez operatora w zleceniu tego reconu („Utrzymanie: CC/API — lokalny model za słaby — rozstrzygnięte"). Nie podważam tego kierunku; on jest już przesądzony. Pytanie, które faza3 zostawia otwarte, to **rytm**: czy kompilacja jest wyłącznie ręcznie wyzwalanym batchem, czy dostaje automatyczny trigger, i jak zamyka się pętla zwrotna (inwariant 4: *„dobre odpowiedzi z zapytań wracają do wiki jako nowe strony"*). ### Trzy tryby, ocenione pod kątem tego, co ta faza faktycznie robi **(A) Czysty batch (sesja CC, ręcznie wyzwalana).** Operator albo agent odpala sesję z listą encji, sesja robi N zapytań do `hybrid_retrieve`/ `cascade_retrieve`, pisze/aktualizuje strony, commituje. Koszt: **przewidywalny i widoczny w moment wywołania** — tyle, ile ta jedna sesja zużyje tokenów API. Częstotliwość: kiedy operator zdecyduje (np. raz na kilka sesji roboczych KB). **(B) Job z pipeline'em eskalacji (per-query, automatyczny).** Każde zapytanie do `kb-query` /search, które trafia próg (`dist`, heurystyki), eskaluje do API i **automatycznie** zapisuje/aktualizuje stronę wiki z odpowiedzi. To bezpośrednio realizuje pętlę zwrotną (inwariant 4) bez udziału człowieka w każdym pojedynczym zapisie. **(C) Hybryda: bootstrap batch, utrzymanie pipeline.** Pierwsza fala stron (PoC, potem szersza kompilacja korpusu) idzie trybem (A) — bo trzeba pokryć setki/tysiące encji z pustego stanu, co jest z natury wsadowe i wymaga przeglądu jakości. Po ustabilizowaniu wiki, dopisywanie/aktualizacja idzie trybem (B), zdarzeniowo, przy okazji zapytań `/search`, które i tak przechodzą przez `kb-query`. ### Rekomendacja: (C), z (B) odłożonym poza zakres tej fazy Uzasadnienie: 1. **Skala nie pozwala na (B) od startu.** `kb-query` dziś **nie robi syntezy odpowiedzi** — to jawnie zapisane w `kb/services/kb-query.md` (*„This is a search API, not chat: no answer synthesis over results, that's phase 5"*). Automatyczny zapis strony z „dobrej odpowiedzi" wymaga najpierw tej syntezy (LLM nad wynikami retrievalu) — a to jest osobny, jeszcze niezbudowany komponent, w planie `kb-m5-faza-mailowa.md` §11 zapisany jako *„Synteza odpowiedzi + polityka eskalacji | faza 5 | po fazie 3/4"*. Innymi słowy: (B) zakłada istnienie warstwy, którą dopiero ta faza ma zbudować. Zaczynanie od (B) to budowanie pipeline'u nad nieistniejącym sygnałem. 2. **Bootstrap wymaga przeglądu, nie automatyzacji.** 17 stron z §3 to pierwsza próba entity resolution na korpusie, który nigdy wcześniej nie był tak czytany — pierwsze przebiegi *będą* mylić się (aliasy tej samej osoby pod różnymi adresami, jak `paweld2@gmail.com` vs `pawel@cosmose.co`, patrz #9). Batch z operatorem czytającym diff przed commitem jest tu właściwym trybem; automatyczny zapis bez przeglądu ryzykuje właśnie to, co inwariant 3 (lint) ma łapać *po fakcie* — lepiej łapać *przed* pierwszym zapisem. 3. **Koszt.** Batch: koszt jednej sesji, znany z góry (faza3 §11 szacuje kompilację 3–5 stron proof jako „pomijalna" wobec ~1.5–4 USD pilota streszczeń). Pipeline per-query: koszt proporcjonalny do ruchu na `kb-query`, dziś zerowego (serwis bez UI logowania, brak ruchu produkcyjnego opisanego w żadnym audycie) — budowanie licznika kosztów pod ruch, którego jeszcze nie ma, jest przedwczesne. **Wniosek: w tej fazie — wyłącznie (A)/(C)-batch.** Pipeline eskalacji (B) to konsekwencja *syntezy odpowiedzi*, która sama jest poza zakresem tego reconu (`kb-m5-faza-mailowa.md` §11) — wpisuję go do §8 (plan iteracji) jako **osobny, późniejszy etap**, nie część pierwszego kroku. ### Trigger i częstotliwość dla batcha Rekomendacja: **ręczny**, przywiązany do sesji roboczych KB, nie do ticku ingestu. Uzasadnienie: `kb-ingest`/`kb-mail-sync` tykają bez nadzoru (systemd timer) właśnie dlatego, że są deterministyczne i tanie (lokalny embed). Kompilacja wiki zużywa API i wymaga jakościowego osądu przy każdym zapisie na tym etapie — sprzeczne z „bez nadzoru". Automatyczny trigger „po ticku ingestu" dodałby niekontrolowany koszt API do procesu, który dziś nie ma żadnego budżetu ani limitu (`packages/kb-retrieval`/`kb-query` nie mają pojęcia o kosztach API — nigdzie w repo nie ma licznika wydatków Anthropica). --- ## 5. Punkt 4 — Format linku dowodowego i odporność na re-chunking ### Co już zdecydowano Faza3 §8.2 daje **dokładną składnię**, cytuję w całości bo to bezpośrednia odpowiedź na pytanie zlecenia: ```markdown --- sources: - envelope_id: paperless:119 chunks: [4211, 4213] # document_chunk.id - envelope_id: "CABtrY-...@mail.gmail.com" chunks: [] # koperta bez chunków (np. sam manifest/headers) --- Treść… składka 1 234 zł/rok [^paperless:119#4212]. ``` Dwa poziomy: `sources:` we frontmatterze (agregat, dla lintu — sprawdza czy *strona* ma dowody) i inline `[^envelope_id#chunk_id]` przy każdym fakcie liczbowym (dla spot-checku — sprawdza czy *zdanie* ma dowód). To jest już odpowiedź na „envelope_id vs chunk-id": **oba, na dwóch poziomach granularności**, nie albo-albo. ### Luka, którą faza3 zostawia: `document_chunk.id` (bigint PK) nie jest stabilny To jest realny problem, którego szkic nie adresuje wprost. `document_chunk.id` to `bigint GENERATED … nextval(...)` — PK generowany przy insercie. Re-chunking (zmiana `chunk_size`/`chunk_overlap`, zmiana modelu embeddingu, poprawka chunkera) w obecnym pipeline'u (`jobs/mail-body-ingest`, `kb/phases/kb-m5-faza3.md` §1.6, Decyzja 1) **usuwa i wstawia na nowo** wiersze `document_chunk` dla przetwarzanej koperty — nowe `id`. Strona wiki napisana dziś z przypisem `[^paperless:119#4212]` po re-chunkowaniu paperless wskazuje na `id`, który już nie istnieje. Sprawdziłem: nie ma w repo żadnego mechanizmu "kotwiczenia" chunka niezależnego od PK (np. hash treści, offset w dokumencie źródłowym). `chunk_index` (kolumna w `document_chunk`) jest stabilniejszy semantycznie (n-ty fragment tej koperty) ale też **nie** jest gwarantowany stabilny — zmiana `chunk_size` przesuwa granice i renumeruje wszystkie chunki tej koperty. ### Ocena: to nie jest luka krytyczna, bo lint już ją łapie — ale warto dociągnąć degradację Inwariant 3 (lint) faza3 §8.2 już projektuje mechanizm wykrywania: *„spot-check N losowych przypisów `[^...]` vs treść chunka w bazie"*. Martwy `chunk_id` po re-chunkingu to dokładnie to, co ten spot-check złapie — `SELECT text FROM document_chunk WHERE id = 4212` zwróci zero wierszy, lint zgłasza to jako znalezisko w `_meta/lint-reports/`. **Mechanizm wykrywający już istnieje w projekcie**, więc rekomendacja to nie „zbuduj nowy system kotwiczenia" tylko **doprecyzowanie zachowania przy trafieniu martwego linku**: **Rekomendacja — dwupoziomowy fallback, nie twardy błąd:** 1. Link **prymarny** to `[^envelope_id#chunk_id]` — precyzyjny, ale kruchy. 2. Gdy `chunk_id` nie istnieje (po re-chunkingu), **fallback do `envelope_id`** — dowód wskazuje na kopertę jako całość ("ten fakt pochodzi z tej wiadomości", zamiast "z tego dokładnie fragmentu"). Zdegradowane, ale wciąż audytowalne, bo `envelope_id` **jest** stabilny (`envelope.id` to Message-ID albo `sha256-…`, nigdy nie zmienia się po insercie — `kb-mail-pillar.md` §3, kontrakt zamrożony). 3. Lint raportuje **liczbę zdegradowanych przypisów** jako osobną metrykę w `_meta/lint-reports/` (nie tylko listę zerwanych linków) — to jest wczesny sygnał "re-chunking coś ruszył, strony trzeba przejrzeć", zanim ktokolwiek zauważy to ręcznie. 4. **Re-chunking = trigger do przeglądu, nie do automatycznej naprawy.** Automatyczne przepisanie `chunk_id` na nowy najbliższy semantycznie chunk wymagałoby ponownego embeddingu i porównania — kosztowne i ryzykowne (może przypisać fakt do złego fragmentu po cichu). Degradacja do `envelope_id` + ręczny przegląd przy najbliższej kompilacji tej strony jest tańsza i bezpieczniejsza. Konsekwencja praktyczna: re-chunking całego korpusu mailowego (227k kopert) jest zdarzeniem rzadkim i świadomym (widziane już raz — Decyzja 1 fazy3, `UNIQUE+model`), nie czymś co dzieje się przy okazji. Skala degradacji przy takim zdarzeniu jest więc *skokowa i rzadka*, nie ciągła — fallback + metryka w locie wystarczą, nie trzeba temu poświęcać osobnej infrastruktury. --- ## 6. Punkt 5 — Anty-propagacja: datowanie, sprzeczności, aktualizacja ### Co już zdecydowano Frontmatter niesie `compiled_at`/`updated_at` (datowanie na poziomie strony). Inwariant 3: lint okresowy łapie *„sprzeczności między stronami (fakty o tej samej encji)"*. Inwariant 4: pętla zwrotna dopisuje nowe strony/aktualizacje z dobrych odpowiedzi zapytań. ### Luki, które faza3 nie precyzuje i które zlecenie wprost pyta **(1) Sekcja „niepewne/sprzeczne" na poziomie strony.** Dziś nic w projektowanym frontmatterze/strukturze nie ma miejsca na "wiem, że dwa źródła mówią co innego, i to jest w rękopisie widoczne". Lint wykrywa sprzeczność *między stronami* — ale co z sprzecznością *wewnątrz* jednej kompilacji (np. paperless:119 podaje jedną kwotę składki, wcześniejszy mail podaje inną — podwyżka, błąd, czy różne polisy)? **Rekomendacja:** sekcja `## Niepewne / sprzeczne` jako **konwencja treści** strony (nie pole frontmattera — to jest proza, nie metadane), umieszczana zawsze na końcu strony, gdy występuje. Format: ```markdown ## Niepewne / sprzeczne - Składka OC: [^paperless:24#N] podaje kwotę roczną bez liczby, [^#M] (mail 2025-03) wspomina "1 450 zł" — nie jest jasne, czy to ta sama polisa czy poprzedni rok. **Nie rozstrzygane automatycznie** — do potwierdzenia przy następnej kompilacji tej strony. ``` Lint (spot-check) traktuje tę sekcję jako **oczekiwaną**, nie jako defekt — strona bez tej sekcji nie jest "lepsza", strona z niewykrytą sprzecznością w treści głównej (fakt podany jako pewny, gdy dowody się rozjeżdżają) jest gorsza. To jest różnica między *ukrywaniem* niepewności (zły kompilat) a jej *nazwaniem* (dobry kompilat) — i to jest właśnie mechanizm, który ma zapobiegać propagacji błędu: LLM kompilujący wprost pisze "nie wiem", zamiast zgadywać i zamrażać zgadywankę jako fakt. **(2) Polityka aktualizacji przy nowym, sprzecznym dowodzie.** Scenariusz ze zlecenia: nowy mail przeczy istniejącej stronie. Dwie opcje: - **Nadpisać** stronę nowym stanem (traktować najnowszy dowód jako prawdziwy) — ryzyko: jeśli nowy mail jest błędny/nieaktualny/o innej sprawie, kasuje się poprawną wcześniejszą treść bez śladu. - **Dopisać do „Niepewne/sprzeczne"** i **nie nadpisywać** faktu w treści głównej, dopóki sprzeczność nie zostanie rozstrzygnięta (przez kolejną kompilację z jeszcze jednym dowodem, albo ręcznie przez operatora). **Rekomendacja: druga opcja, z jednym wyjątkiem.** Domyślnie *dopisz, nie nadpisuj* — bo strona ma być audytowalnym destylatem, nie najświeższą migawką, a "nowszy mail" nie zawsze znaczy "poprawniejszy" (może być o innej polisie, literówka nadawcy, spam). Wyjątek: gdy nowy dowód jest **tego samego typu i wprost aktualizuje poprzedni** (np. "nowy cennik od 1.09" jawnie zastępujący poprzedni, nie sprzeczny z nim) — to nie jest sprzeczność, to aktualizacja, i idzie do treści głównej z nową datą `updated_at`, a stary fakt przechodzi do sekcji „Historia" (jeśli strona jej potrzebuje) albo po prostu znika, zastąpiony — to jest zwykła kompilacja, nie przypadek antypropagacji. Rozróżnienie "to jest aktualizacja" vs "to jest sprzeczność" robi **sesja kompilująca (CC/API)**, nie automat — zgodnie z inwariantem 2 (kompilacja to zadanie dla modelu, nie dla reguły). **(3) Datowanie twierdzeń w treści, nie tylko w frontmatterze.** Rekomendacja dodatkowa: każdy fakt z konkretną datą ważności (cena, status sprawy, "aktualnie w toku") niesie datę **przy fakcie**, nie tylko w `updated_at` strony — bo strona może zbierać fakty z różnych momentów (mail z 2019 o starej umowie + mail z 2026 o nowej), a jedna data na całą stronę zaciera to rozróżnienie. Przykład: *„Składka 1 234 zł/rok (stan na 07.2026) [^paperless:119#4212]."* To jest rozszerzenie konwencji przypisu z §5, nie nowy mechanizm. --- ## 7. Punkt 6 — Integracja z kb-query: retrieval, ryzyko sprzężenia ### Co już zdecydowano Inwariant 5 (faza3 §8.1): *„Wiki wchodzi do retrievalu jako dodatkowe źródło… kaskada: wiki → summary → chunk."* §8.2 rozwija: strona wiki = koperta `source='wiki'`, `id='wiki:<ścieżka>'`, chunkowana i embedowana tym samym jobem, **bez nowego schematu bazy** — reużywa istniejący kontrakt koperty. ### Co to znaczy konkretnie w kodzie dzisiejszego `packages/kb-retrieval` Sprawdziłem `retrieval.py`: dziś są trzy tory (`flat`/`cascade`/`hybrid`), z `cascade_retrieve` jako **dwupoziomowym** (`document_summary` → `document_chunk`) i `hybrid_retrieve` jako cascade + równoległy skan `summaryless_sources` (`gmail`, `fastmail`) scalany po `dist`. **Nie ma dziś trzeciego poziomu.** Realizacja inwariantu 5 wymaga: 1. Nowej tabeli/kolekcji `document_summary`-podobnej dla wiki, **albo** traktowania stron wiki jak paperless — envelope z summary (skoro strony są krótkie, 1–3 chunki, `document_summary` dla całej strony ma sens semantyczny inaczej niż dla 187k-wierszowego newslettera). 2. Nowej funkcji `wiki_retrieve` w `kb_retrieval/retrieval.py`, wołanej **przed** `cascade_retrieve`, z wynikiem scalanym analogicznie do `hybrid_retrieve` (ten sam embedder, ten sam cosine space — merge to sort, nie renormalizacja, dokładnie jak dziś). 3. Zmiany w `search.py`/`app/main.py` `kb-query`, żeby nowy tryb (`mode`) albo rozszerzenie `hybrid` obejmował wiki — i w `app/links.py` (`build_result`) żeby wynik ze źródła `wiki` miał sensowny `link` (`raw_ref` do pliku w `kb-wiki`, nie URL — bo `kb-wiki` nie jest hostowane publicznie, patrz §2). To jest **realna zmiana kodu** w serwisie produkcyjnym (`kb-query`), nie tylko w nowym repo `kb-wiki` — warto to mieć jawnie w planie iteracji (§8), bo inwariant 5 bez tego pozostaje deklaracją, nie działaniem. ### Ryzyko sprzężenia (self-citation / citogenesis) — luka w szkicu faza3 Zlecenie pyta wprost o to ryzyko i faza3 **go nie adresuje**. Mechanizm ryzyka: kompilacja strony X czyta wyniki retrievalu (inwariant 2) → jeśli retrieval w tym momencie już przeszukuje `source='wiki'` (inwariant 5, w pełni wdrożony) → strona X może dostać jako "dowód" **inną stronę wiki**, która sama została skompilowana z niepewnych/błędnych przesłanek → błąd się utrwala i **wzmacnia** zamiast zanikać, bo kolejna kompilacja cytuje już nie surowy mail, tylko wcześniejszą (błędną) syntezę, z pozorem niezależnego potwierdzenia. To jest dokładnie mechanizm Wikipedia-citogenesis, przeniesiony na kompilator jednoosobowy. **Rekomendacja — rozdzielić dwie ścieżki retrievalu, które dziś inwariant 5 zlewa w jedną:** 1. **Retrieval na potrzeby `/search` (użytkownik, faza 5 synteza odpowiedzi)** — tu wiki **powinna** wchodzić do kaskady (inwariant 5 ma rację: skompilowana wiedza jest cenniejsza niż surowy chunk dla kogoś, kto pyta "co wiem o PZU"). 2. **Retrieval na potrzeby *kompilacji* nowej/aktualizowanej strony (sesja CC/API pisząca `kb-wiki`)** — tu retrieval **musi** wykluczać `source='wiki'` (parametr `exclude_sources=('wiki',)` w `cascade_retrieve`/`hybrid_retrieve`, jedna linia zmiany analogiczna do dzisiejszego `summaryless_sources`). Kompilacja zawsze czyta wyłącznie warstwę dowodową (mail, paperless — evidence layer z ustaleń wyjściowych operatora), nigdy inne strony wiki jako źródło faktów. Strony **mogą** linkować się nawzajem przez `[[...]]` (graf, nawigacja) — ale to nie jest to samo co "cytowanie jako dowód faktu"; link `[[pzu]]` w treści strony `polisa-oc-auto` to odsyłacz do powiązanej strony, nie przypis źródłowy — przypisy źródłowe (`[^...]`) zawsze wskazują `envelope_id`, nigdy `wiki:...`. Ten podział jest tani do wymuszenia (jeden parametr) i eliminuje ryzyko przy źródle, zamiast polegać wyłącznie na lincie żeby je złapać po fakcie. Rekomendacja: dopisać to jako **inwariant 7** przy zatwierdzaniu planu, nie jako implementacyjny szczegół — to jest decyzja architektoniczna tej samej wagi co inwarianty 1–6 z faza3 §8.1. --- ## 8. Punkt 7 — Zależności i kolizje ### Multiagent recon / dyspozytor Sprawdzone bezpośrednio w `kb/subsystems/recon-multiagent.md` i potwierdzone przez `kb/audits/mail-sync-2026-08-06.md` §5 (który zrobił dokładnie to samo ćwiczenie dla przyrostówki): **dyspozytor subsystemu B nie istnieje jeszcze w kodzie** — jest zdefiniowany jako *osobny projekt* w `kb/decisions/architektura-2026-07-28.md` (*„B — do-the-work: dyspozytor zadań (agent)… Osobny wysiłek, osobny projekt"*), planowany na PIHA. `kb-query` występuje w reconie multiagentowym wyłącznie jako wiersz inwentarza, poza zakresem control-plane/Telegram. **Wniosek: zero zależności blokujących w żadną stronę**, z tym samym zastrzeżeniem, jakie mail-sync-recon już zanotował dla siebie: nowy automat (tu: ewentualny periodyczny lint albo — gdyby kiedyś powstał — pipeline eskalacji z §4) **dokłada się do „shadow set"** bytów poza GitOps-ową detekcją dryfu (`kb-ingest`, `stability-agent`, teraz `kb-mail-sync`, potencjalnie `kb-wiki`-lint). Nie blokuje nic dzisiaj; warto, żeby trafiło świadomie do otwartego pytania nr 5 tamtego reconu, a nie przez przeoczenie — dokładnie tak samo jak zanotował to mail-sync-recon dla siebie. **Telegram/approval flow** (`CLAUDE.md` „Action approval flow") dotyczy wyłącznie akcji `container_restart`/`redeploy`/`disk_cleanup`/`alert_only` z control-plane. Kompilacja wiki nie generuje żadnej z tych akcji — nie ma i nie powinno być punktu styku. Jedyny sensowny alert, gdyby faza 5 dostała automatyzację (§4 tryb B, poza zakresem tej fazy), byłby analogiczny do `KbMailSyncStale`: "kompilator/lint nie chodzi", nie coś wymagające approvalu operatora. ### kb-site publish Rozstrzygnięte w §2: `kb-wiki` leży poza `kb/**/*.md`, więc `gen_pages.py`/ `publish.sh` **nie widzą go w ogóle** — zero kolizji, bo zero styku. Gdyby w przyszłości ktoś położył treść `kb-wiki` wewnątrz `kb/` (odwrotnie od rekomendacji §2), *wtedy* powstałaby realna kolizja: `gen_pages.py --check` skanuje wygenerowany output pod kątem wycieków (IP, tokeny, ścieżki hosta) — ale nie skanuje pod kątem **danych osobowych** (PESEL, adresy, numery umów), bo nie taki był jego cel. To jest dodatkowy, mocny argument za §2 (osobne repo): trzymanie wiki-encji poza `kb/` eliminuje całą tę klasę ryzyka strukturalnie, zamiast polegać na tym, żeby nikt nigdy nie oznaczył strony o PZU jako `visibility: public` przez pomyłkę. ### Follow-upy fazy mailowej — czy brudne chunki psują kompilację Z `kb/incidents/2026-08-26-mail-sync-20-dni-ciszy.md` §6 (`Nie w zakresie tej naprawy`, bez zmian od 08-06): **charset/mojibake w co najmniej 12 kopertach** oraz **NUL w nagłówkach `jsonb`** pozostają otwarte. Oceniam wpływ na wiki-kompilat: - **Skala jest znikoma** — 12 kopert na 227 576 (≈0,005%). Statystycznie prawdopodobieństwo, że pierwsza fala 17 stron z §3 natrafi akurat na jedną z tych 12, jest bliskie zeru (żadna z zidentyfikowanych encji §3 nie pochodzi z okresu/nadawcy powiązanego ze znanymi przypadkami mojibake wg audytu 08-06). - **Mechanizm szkody, gdyby trafiła:** kompilująca sesja CC dostaje chunk z zepsutym kodowaniem jako "dowód" — najgorszy scenariusz to nie błędny fakt (model raczej rozpozna nieczytelny tekst i pominie/zaznaczy niepewność, patrz §6 wyżej), tylko **marnotrawstwo** — chunk wygląda jak szum, model go ignoruje, dowód dla realnego faktu ginie z pola widzenia. - **NUL w `entities` jsonb jest poważniejszy strukturalnie** — jeśli sesja kompilująca odpytuje `entities` bezpośrednio (nie tylko `document_chunk.text`), NUL bajt w jsonb może wywrócić parser po stronie klienta (Pythonowy `json`/`asyncpg` różnie reaguje na `` w zależności od ścieżki). To **nie zostało zweryfikowane w tym reconie** (nie testowałem odczytu takiego wiersza) — flaguję jako `[do weryfikacji]`, nie jako potwierdzony problem. **Rekomendacja: nie blokować pierwszego etapu na R1–R3 z §6 incydentu** (charset/NUL) — skala nie uzasadnia opóźnienia PoC. Warto natomiast, żeby lint (inwariant 3) przy pierwszym uruchomieniu zrobił **jednorazowy skan** `document_chunk`/`envelope.entities` pod kątem znaków zastępczych (`�`) i `` w źródłach, które faktycznie zasiliły PoC (17 stron §3) — tanie zabezpieczenie, żeby nie odkryć problemu dopiero przy spot-checku tygodnie później. --- ## 9. Punkt 8 — Plan iteracji Wzorzec: faza mailowa (`kb-m5-faza-mailowa.md` §10 „Plan implementacji" + §12 kryterium ukończenia per krok) i mail-sync-recon (§6 „Podsumowanie i zakres pracy" — tabela kroków z rodzajem zmiany). Stosuję ten sam format: kroki z zależnościami, DoD per krok, pierwszy etap ograniczony do 1–2 sesji. ### Etap 0 — Zamknięcie decyzji (ten dokument + zatwierdzenie, 0 sesji kodu) Operator przyklepuje §11 (Decyzje) poniżej. Bez tego nic dalej nie rusza — identyczny wzorzec jak Decyzje (a)-(g) w mail-sync-recon, które **zatwierdzono w całości** przed implementacją (`kb-m5-faza-mailowa.md` nagłówek statusu). ### Etap 1 — Szkielet `kb-wiki` + 3 strony proof (**cel: 1 sesja**) Zakres, celowo mniejszy niż faza3 §8.2 (3–5 stron) i mniejszy niż lista z §3 (17 encji) — pierwszy etap ma zweryfikować *mechanizm*, nie pokryć korpus: | # | Element | DoD | |---|---|---| | 1 | Repo `kb-wiki` (git, poza `homelab-codex-ws`), struktura katalogów wg faza3 §8.2, `_meta/conventions.md` (format frontmattera + inline-przypisów z §5, w tym reguła fallback z §5, sekcja „Niepewne/sprzeczne" z §6, rozdział retrievalu z §7 inwariant 7) | Repo istnieje, `conventions.md` kompletny, czytelny bez kontekstu tej sesji | | 2 | Walidator `kb-wiki/check_okf.py` — wariant lekki, wzorowany na `~/narty-2027/saalbach-kb/check_okf.py` (frontmatter parsowalny + `type` niepuste), rozszerzony o sprawdzenie bloku `sources` (niepusty, każdy `envelope_id` istnieje w bazie — może wymagać `KB_DSN`) | `python3 check_okf.py` PASS na repo z krokiem 3 | | 3 | **3 strony proof**, kompilowane ręcznie sesją CC z wyników `hybrid_retrieve`/`cascade_retrieve` na encjach #1 (`fll-2025-26`), #5 (`mbank`), #9 (`pawel-cesar-sanjuan-szklarz`) z §3 — świadomie zróżnicowane typy (sprawa/podmiot/osoba-z-aliasami) i źródła (paperless/gmail-długi-ciąg/gmail-multi-adres) | Każda strona: frontmatter zgodny z konwencją, ≥1 przypis inline, `sources:` niepuste, przechodzi walidator | | 4 | Ręczny spot-check (inwariant 3, wersja manualna — pełny lint to Etap 2) — operator albo agent czyta 3 strony vs źródłowe chunki, potwierdza brak halucynacji | Zapisany w `_meta/lint-reports/2026-08-XX.md` — pierwszy raport, nawet jeśli ręczny | **Dlaczego te trzy, nie 17:** `pawel-cesar-sanjuan-szklarz` (#9) testuje entity resolution na aliasach — jeśli to zawiedzie, cała reszta listy z §3 (gdzie kilka pozycji ma podobny problem, np. `cosmose`↔#7/#8) wymaga przeprojektowania konwencji *zanim* zainwestuje się w kolejne 14 stron. Trzy strony, świadomie różne, są tańszym testem niż siedemnaście podobnych. ### Etap 2 — Lint automatyczny + rozszerzenie do pełnej listy §3 (**2. sesja**) Zależy od Etapu 1 (mechanizm zweryfikowany). Zakres: 1. `kb-wiki/lint.py` — implementacja inwariantu 3 w kodzie (nie ręcznie): linki `[[...]]` do nieistniejących stron, strony-sieroty, spot-check N losowych przypisów (zapytanie do `document_chunk` po `id`, fallback do `envelope_id` z §5 gdy `id` martwy — z metryką degradacji), sprzeczności między stronami (heurystyka: te same encje w `sources:` różnych stron z rozbieżnymi liczbami przy tym samym typie faktu — pełna detekcja semantyczna to zadanie dla CC, nie dla skryptu; skrypt robi tylko pre-filtr kandydatów). 2. Rozszerzenie o pozostałe encje z §3 (do 17), plus jednorazowy skan pod kątem `�`/`` (§8 „follow-upy fazy mailowej") na źródłach, które faktycznie zasiliły te strony. 3. **DoD etapu**: `lint.py` uruchamiany ręcznie (nie timer — zgodnie z §4, automatyzacja poza zakresem), raport w `_meta/lint-reports/`, zero krytycznych znalezisk nierozwiązanych (martwe linki, sieroty) — degradacje chunk-id i sekcje „niepewne" to oczekiwany, nie błędny stan. ### Poza zakresem pierwszych dwóch etapów (świadomie odłożone) | Temat | Gdzie wraca | |---|---| | Integracja `source='wiki'` w `packages/kb-retrieval` (§7, inwariant 5 + 7) | Etap 3 — wymaga działającego korpusu wiki (≥Etap 2) zanim ma sens dodawać go do retrievalu produkcyjnego | | Synteza odpowiedzi (`/search` → odpowiedź w naturalnym języku) | Osobna faza, `kb-m5-faza-mailowa.md` §11 już to tak kotwiczy | | Pipeline eskalacji per-query (tryb B z §4) | Po syntezie odpowiedzi — bez niej nie ma sygnału do eskalacji | | Automatyzacja lintu (timer) | Po Etapie 2, gdy wiadomo ile realnie kosztuje jeden przebieg | | Ewentualny hosting/publikacja `kb-wiki` | Nie zaplanowane — patrz §2, dziś brak potrzeby | | Druga tura encji (#18-20 z §3, `outbox.pl`/dawny pracodawca) | Po ustaleniu kontekstu firmy — osobne mini-śledztwo | --- ## 10. Punkt 9 — Decyzje operatora Wzorzec (a)-(g) z `kb/audits/mail-sync-2026-08-06.md` §4. ### (a) Wersja OKF dla `kb-wiki`: pin v0.1 (jak pilot) vs start od razu na v0.2 **Rekomendacja: pin v0.1, z migracją zaplanowaną, nie natychmiastową.** - Pilot narty27 **potwierdził w praktyce**, że pinowanie v0.1 działa i że zmiana jest tania: `docs/sessions/2026-07-31-kb-f4-final-narty27.md` — *„OKF v0.1 działa w praktyce, a pinowanie wersji okazało się słuszne — spec ewoluuje (v0.2: `timestamp` → `generated: {by, at}`, provenance first-class). Przy fazie 5 rozważyć start od razu na v0.2 albo pin v0.1 z zaplanowaną migracją."* To jest bezpośrednia rekomendacja z pilota, cytowana w zleceniu tego reconu. - **Ale**: `kb-wiki` ma silniejszą potrzebę provenance niż narty27 (który jest planowaniem wyjazdu, nie audytowalnym kompilatem nad wrażliwym korpusem). Provenance first-class w v0.2 (`generated: {by, at}`) pokrywa się częściowo z tym, co faza3 §8.2 już projektuje osobno (`compiled_by`/`compiled_at` w `sources`-owym frontmatterze) — czyli **potrzeba, którą v0.2 adresuje, została już zaadresowana lokalną konwencją**, niezależnie od wersji OKF. To osłabia argument "musimy mieć v0.2 od razu", bo pole `compiled_by` robi tę samą robotę. - Decydujący argument za v0.1: **`homelab-codex` samo jest dziś na v0.1** (`scripts/kb/check_okf.py`, `PINNED_OKF = "0.1"`). Gdyby `kb-wiki` (osobne repo, ale koncepcyjnie ta sama rodzina dokumentów co `kb/`) startował na v0.2, powstałyby **dwie różne wersje specyfikacji w tym samym ekosystemie narzędzi** — dwa parsery, dwie reguły walidacji, mylące przy przenoszeniu wzorców między repo (dokładnie to, co uzasadnia rekomendację pilota "pin + zaplanowana migracja" zamiast "zawsze najnowsza wersja"). Spójność z resztą ekosystemu waży więcej niż wyprzedzenie o jedną wersję specyfikacji, którą i tak trzeba będzie kiedyś zmigrować w `kb/` też. - **Migracja zaplanowana**: gdy `homelab-codex`/`kb/` migruje na v0.2 (osobna decyzja, poza zakresem tego reconu), `kb-wiki` migruje w tym samym oknie — jedna zamiana pola (`timestamp`→`generated: {by,at}`), zgodnie z oceną pilota że to tania zmiana. ### (b) Repo `kb-wiki` — potwierdzenie D7 z faza3 **Rekomendacja: potwierdzić bez zmian** (§2). Jedyne doprecyzowanie: kompilacja nie idzie przez `agent.sh`-owy worktree — osobny tryb pracy z dwoma klonami obok siebie, do zapisania w `kb-wiki/_meta/conventions.md`. ### (c) Tryb kompilatora — potwierdzenie batch/hybryda, pipeline eskalacji poza zakres **Rekomendacja:** (§4) batch (CC/API, ręczny trigger) dla bootstrapu i utrzymania w tej fazie; pipeline eskalacji per-query (tryb B) świadomie odłożony do momentu, gdy istnieje synteza odpowiedzi w `kb-query` — bez niej nie ma sygnału, który miałby eskalować. ### (d) Fallback chunk-id → envelope_id przy re-chunkingu **Rekomendacja:** (§5) dwupoziomowy link z degradacją do `envelope_id`, lint raportuje liczbę zdegradowanych przypisów jako osobną metrykę, re-chunking traktowany jako trigger do ręcznego przeglądu, nie do automatycznej naprawy. ### (e) Sekcja „Niepewne/sprzeczne" i polityka aktualizacji — dopisać do konwencji **Rekomendacja:** (§6) sekcja jako konwencja treści (nie pole frontmattera), domyślna polityka „dopisz, nie nadpisuj" przy sprzecznym nowym dowodzie, wyjątek dla jawnych aktualizacji tego samego faktu, rozróżnienie robi sesja kompilująca. ### (f) Inwariant 7 — rozdział retrievalu kompilacji od retrievalu zapytań **Rekomendacja:** (§7) dopisać do inwariantów faza3 §8.1 jako inwariant 7: kompilacja strony wiki **nigdy** nie czyta `source='wiki'` jako dowód (`exclude_sources=('wiki',)`), tylko `/search` (warstwa użytkownika, po zbudowaniu syntezy odpowiedzi) widzi wiki w kaskadzie. Mitygacja self-citation/citogenesis przy źródle, nie tylko przez lint po fakcie. ### (g) Zakres i kolejność Etapu 1 — 3 strony proof, nie 17 **Rekomendacja:** (§9) Etap 1 = mechanizm (3 strony celowo zróżnicowane: sprawa/podmiot/osoba-z-aliasami), Etap 2 = skala (do 17) + lint w kodzie. Rozszerzenie listy encji (#18-20, `outbox.pl`) odłożone do osobnego mini-śledztwa kontekstu firmy. ### (h) Follow-upy fazy mailowej (charset/NUL) — nie blokują, jednorazowy skan przy Etapie 2 **Rekomendacja:** (§8) nie opóźniać PoC na R1–R3 z incydentu 20-dni-ciszy; dopisać jednorazowy skan `�`/`` do zakresu Etapu 2 (lint), nie jako osobny, wcześniejszy krok. --- ## 11. Podsumowanie Korpus mailowy dogonił dziś — blokada z `kb-m5-faza3.md` §8.1 jest zdjęta (§1). Architektura wiki-kompilatu jest w dużej mierze **już zaprojektowana i zatwierdzona** (`kb-m5-faza3.md` §8, decyzja 2026-07-15/17) — ten recon weryfikuje ją na aktualnym stanie repo, dociąga trzy realne luki, których tamten szkic nie adresował (**chunk-id vs re-chunking** §5, **self-citation przy retrievalu kompilacyjnym** §7 — nowy inwariant 7, **sekcja niepewne/sprzeczne** §6), i daje 17-pozycyjną listę encji zalążkowych wyprowadzoną z realnych SELECT-ów na żywym korpusie zamiast z trzech przykładów w szkicu (§3). Rekomendowany pierwszy etap to **3 strony proof w 1 sesji** (świadomie mniej niż faza3 §8.2 sugerowała, bo pierwszy test ma zweryfikować mechanizm entity-resolution na trudnym przypadku — aliasach — zanim zainwestuje się w skalę), z jawnym odłożeniem pipeline'u eskalacji i integracji retrievalu produkcyjnego do kolejnych etapów. Do decyzji operatora: (a)-(h) w §10.