homelab-codex-ws/kb/audits/wiki-kompilat-recon-2026-08-26.md
oskar efd9ad9443 docs(kb): faza5-wiki Etap 1 + odkrycie: kb-wiki proof-of-concept już istniał od 2026-07-21 na Forgejo, audyt 08-26 tego nie wykrył
kb/phases/kb-m5-faza5-wiki.md dokumentuje: (1) tę sesję (2026-08-27) budującą
niezależny lokalny bootstrap kb-wiki (3 strony: fll-2025-26, mbank,
pawel-cesar-sanjuan-szklarz) nie wiedząc o realnym repo oskar/kb-wiki na
Forgejo (5 stron, od 2026-07-21, dokumentowanym już w kb-m5-faza4.md i
docs/sessions/2026-07-21.md); (2) jak doszło do odkrycia (audyt 08-26 nie
sprawdził Forgejo, tylko repo lokalne + PIHA); (3) porównanie obu kompilacji
fll-2025-26 (komplementarne, nie sprzeczne); (4) rekoncyliację jako decyzję
operatora, nie tej sesji — lokalny bootstrap nigdy nie pushowany do żadnego
remote.

Jednolinijkowa notka przy audycie §9/Etap 1 wskazuje na pełne rozliczenie.
2026-08-27 16:59:08 +02:00

857 lines
48 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
okf: "0.1"
type: audit
visibility: private
status: active
updated: 2026-08-27
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: <strona><envelope_ids>`) —
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, 310 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 20112012 (~23k/rok — dawny pracodawcy przez `outbox.pl`), potem
stabilne 913k/rok 20132025. 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 | 20052020 |
| Oskar Kapala | oskar.kapala@outbox.pl | 6 399 | (epoch)2014 |
| Kasia Lorenc-Kapala | katalia@gmail.com | 3 385 | 20072026 |
| Oskar Kapala | oskar.kapala@gmail.com | 2 910 | 20102026 |
| System Synergia | robot2@robot.librus.pl | 2 818 | 20182026 |
| Allegro | powiadomienia@allegro.pl | 2 170 | 20082026 |
| Tomasz Anuszewski | tomasz.anuszewski@outbox.pl | 1 913 | 20112012 |
| Dziennik Bankier.pl (×2 adresy) | …bankier.pl | 1 736 + 1 676 | 20072014 |
| RSW_TECH_TEAM | rsw_tech_team@outbox.pl | 1 628 | 20112012 |
| Groupon | info@news.groupon.pl | 1 447 | 20112012 |
| Miron Mironiuk | m@cosmose.co | 1 126 | 20152016 |
| kontakt@mbank.pl | | 1 099 | 20052026 |
| Jan Boboli | jan.boboli@outbox.pl | 969 | 20112013 |
| Paweł Cesar Sanjuan Szklarz | paweld2@gmail.com, pawel@cosmose.co | 757+555+418+376 | 20052024 |
| InPost | info@paczkomaty.pl | 722 | 20172026 |
| Bogumil Jakubiak | B.Jakubiak@icm.edu.pl | 686 | 20042009 |
| sOKratis Liliana Banaszak | liliana.banaszak@sokratis.pl | 679 | 20092026 |
| Strava | update.strava.com / strava.com | 591+395 | 20172026 |
**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 (20112012), 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 1020 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 20052026, 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 20182026, 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, 20172026 ciągłe |
| 13 | `bogumil-jakubiak` | osoba | ICM UW, 20042009 — 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 20092026, ciągłość 17 lat, nazwany kontakt (Liliana Banaszak) |
| 17 | `allegro` | temat | 2 170 kopert 20082026 — najdłuższa ciągła relacja handlowa w korpusie |
| 1820 | (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ę 35 stron proof jako „pomijalna" wobec ~1.54 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, [^<msg-id>#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, 13 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 16 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 R1R3 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
(`<60>`) 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 12 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**)
> **Wykonany 2026-08-27 — z zastrzeżeniem: ten audyt nie wykrył, że proof-of-concept
> (5 stron) już istniał od 2026-07-21 na Forgejo (`oskar/kb-wiki`, poza zestawem
> dowodów tego reconu). Pełne rozliczenie i rekoncyliacja: `kb/phases/kb-m5-faza5-wiki.md`.**
Zakres, celowo mniejszy niż faza3 §8.2 (35 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 `<60>`/`` (§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
> **Status: (a)-(h) ZATWIERDZONE przez operatora w całości 2026-08-27, bez
> zmian** — wzorzec identyczny jak decyzje (a)-(g) w `mail-sync-2026-08-06.md`
> (zatwierdzone 2026-08-06 w całości, patrz `kb-m5-faza-mailowa.md` nagłówek
> statusu).
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 R1R3 z incydentu 20-dni-ciszy;
dopisać jednorazowy skan `<60>`/`` 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.
Decyzje: (a)-(h) w §10 — **ZATWIERDZONE przez operatora w całości 2026-08-27,
bez zmian.**