Files
kolaytercih/docs/gece-vardiyasi/2026-09-21/08-kod-denetimi.md
bilalgursen 0ab4dbe88c
All checks were successful
Deploy / deploy (push) Successful in 7m18s
Enhance Traefik configuration for www.kolaytercih.com
Added a new router and middleware for handling www to apex domain redirection, ensuring that Let's Encrypt certificates are obtained for both domains. Updated the docker-compose.yml file to include necessary rules and middleware for proper redirection and TLS handling.

Also, updated the CEO agent description to remove redundant details and clarified the QA engineer's responsibilities regarding testing critical flows and error reporting. Adjusted the CTO agent's section for consistency in expert guidance and clarified the designer's tools to include 'Skill' for better design resource management.
2026-09-21 20:37:12 +03:00

50 lines
15 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.
# 08 — Kod denetimi (CTO / bağımsız denetçi) — 2026-09-21
Kapsam: `gece/p1-rehber-altyapi`, `gece/p2-olcum`, `gece/p3-vaat-dili`, `gece/p4-eposta` (hepsi `56925e1` tabanlı). Kod **değiştirilmedi**, commit atılmadı. Yöntem: `git diff 56925e1..<dal>` + worktree'den tam dosya okuma + kütüphane kaynağı (`better-auth 1.6.23` magic-link / `with-hooks`, `resend 6.18`) + scratchpad'de `tsx` betikleri (P1 parser'ı 24 ve 27 dosyalık iki içerik kümesinde; P4 guard matrisi; `docker compose config` ile env dosyası ayrıştırma) + `sqlite3 -readonly` (`data/yokatlas.db`, `data/app.db`).
Şeffaflık notu: `entegrasyon` worktree'sinde yanlışlıkla iki salt-okunur git komutu (`git log`, `git status --short`) çalıştırdım; dosya değişmedi, build'e etkisi beklenmez.
## 1. Hükümler
| Dal | Hüküm | Gerekçe |
|---|---|---|
| **P1** `gece/p1-rehber-altyapi` | **MERGE EDİLEBİLİR** | Parser 24 mevcut + 3 yeni yazıda (7 `guncelleme` alanı dahil) hatasız koştu; yalnız hedeflenen 2 açıklama değişti; hydration/catch/sitemap mantığı temiz. |
| **P2** `gece/p2-olcum` | **MERGE EDİLEBİLİR** | Callback ref + React 19 temizliği, kuyruk zamanlayıcısı ve PII kuralları doğru; yalnız düşük önemli ölçüm sapması riskleri var. Son commit (identify) Bilal kararı. |
| **P3** `gece/p3-vaat-dili` | **DÜZELTMEYLE** | İlk iki commit temiz; `52520ac` doğrulanmamış bir rakamı "Armut, 2026" diye kaynak göstererek yayına sokuyor — doğrulanmadan alınmamalı. Ayrıca tadımlık `gerekce`/`trendOzeti` DB metinleri eski dille kalıyor (paket amacı eksik kapanıyor). |
| **P4** `gece/p4-eposta` | **MERGE EDİLEBİLİR (koşullu)** | Guard mevcut prod env'inde (anahtar + `EMAIL_FROM` dolu) girişi kırmıyor; `EMAIL_FROM` fiilen zaten zorunluydu. Koşul: lokal docker testi + P2'nin `e1bb482` commit'iyle **birlikte** merge (yoksa 30. sn'de "Yeniden gönder" sessizce yutulur). |
Kritik bulgu: **yok.** Yüksek bulgu: **1** (P3 `52520ac`).
## 2. Bulgular (en ciddiden)
| # | Dal + commit | Dosya:satır | Sorun | Somut hata senaryosu | Önem | Önerilen düzeltme | Kesinlik |
|---|---|---|---|---|---|---|---|
| 1 | P3 `52520ac` [Bilal] | `src/app/page.tsx:478-480` (+ `:92,168,474`, `src/app/paket/page.tsx:52`) | Doğrulanmamış fiyat aralığı gerçek bir şirkete atfediliyor: "Danışman fiyat aralığı: Armut, 2026." Yazılımcı raporu rakamın açılıp teyit edilmediğini kendisi söylüyor. | Merge → ana sayfada üçüncü tarafa atıflı, kaynağı olmayan rakam yayında; Armut sayfası farklı aralık gösteriyorsa yanıltıcı karşılaştırmalı reklam / yanlış atıf. | **yüksek** | Bu commit'i Bilal kaynağı açıp teyit edene kadar alma. `9d450c5` ondan bağımsız: `git merge-tree --merge-base=52520ac cae2aaa 9d450c5` temiz → `git merge cae2aaa` + `git cherry-pick 9d450c5`. Teyit edilirse kaynak satırına URL/erişim tarihi ekle. | DOĞRULANDI (kod + rapor) |
| 2 | P3 `cae2aaa` | `src/lib/tadimlik-havuzu.ts:84-93` | Yalnız `riskNotu` deterministik yapıldı; aynı satırın `gerekce` ve `trendOzeti` alanları hâlâ DB'deki LLM metni. Yorumdaki "olasılık dili sızamaz" iddiası yanlış. | Lokal `data/app.db` `tadimlik_havuzu`: 514 satırın **13**'ünde `gerekce` "yerleşme olasılığı yüksektir / yüksek ihtimal" içeriyor, **126**'sında `trend_ozeti` "Son 4 yılda…" diyor. Girişsiz ziyaretçi /sonuc'ta "yerleşme olasılığı yüksektir" + sayfanın başka yerinde "son 5 yıl" görür. | orta | Ya `pnpm tadimlik --force` (yeni prompt'la) prod'da koşulmalı (Bilal; bellek notundaki prod koşusu zaten bekliyor), ya da okuma anında `gerekce`/`trendOzeti` için `/olasılı|ihtimal/i` eşleşen satırlar atlanmalı / `trendOzeti` de `trendOzetiUret` benzeri deterministik üretilmeli. Yorum satırı düzeltilmeli. | DOĞRULANDI (lokal DB); prod içeriği OLASI |
| 3 | P4 `2af4a9f` × P2 `e1bb482` | `src/lib/auth.ts:66-77` × `giris-form.tsx:41-44` | Paketler arası bağımlılık: sunucu 60 sn soğumada isteği sessizce 200 ile yutuyor. P2'nin 60 sn geri sayımı yoksa istemci 30. sn'de yeniden göndermeye izin veriyor. | P4 tek başına merge → kullanıcı 30. sn'de "Yeniden gönder" → "gönderildi" ekranı, mail çıkmaz, kullanıcı gelmeyecek maili bekler. (İkisi birlikteyken: istemci sayacı yanıt geldikten sonra, sunucu damgası istek başında başlar → istemci 60 sn ≥ sunucu 60 sn, tutarlı.) | orta (yalnız ayrı merge edilirse) | P2'yi P4'ten **önce ya da aynı anda** merge et. Kalan kenar durum (sayfa yenileme / ikinci cihazdan 60 sn içinde tekrar istek → sessiz başarı) kabul edilebilir: ilk mail zaten gitti ve 15 dk geçerli. | DOĞRULANDI |
| 4 | P4 `5992cf3` | `src/lib/eposta-gonder.ts:47-55` | Prod guard'ın 500 döndürdüğü koşullar (betikle denendi): (a) `RESEND_API_KEY` yok **ya da boş string** ve `EPOSTA_KONSOL_FALLBACK≠1`; (b) anahtar var ama `EMAIL_FROM` yok/boş. | Gitea'da `RESEND_API_KEY` ya da `EMAIL_FROM` secret'ı silinir/yeniden adlandırılırsa CI satırı `EMAIL_FROM=` (boş) yazar → her magic-link isteği 500, e-postayla giriş kapalı (Google etkilenmez). **Bugünkü prod'da kırılma beklenmez:** `04-mail-testi.md` canlı mailin `giris@mail.kolaytercih.com`'dan geldiğini gösteriyor → iki secret da dolu. Eski kodda da boş `EMAIL_FROM` `??`'yi geçip Resend'e `from:""` gidiyordu → zaten hata; yani `EMAIL_FROM` **yeni zorunlu hale gelmedi**, yalnız hata görünür oldu. CI her iki satırı da yazıyor (`ci.yaml:27-28`), Dockerfile env gömmüyor, runtime `env_file: .env.production`. | düşük (bilgi) | Merge öncesi Gitea'da `RESEND_API_KEY` ve `EMAIL_FROM` secret'larının dolu olduğunu gözle teyit et (değerine bakmadan, varlığına). Lokal docker testi şartname §5'teki gibi. | DOĞRULANDI |
| 5 | P2 `cf19903` | `src/features/sihirbaz/components/cta-sira-form.tsx:80,168` | `useTercihProfili` `useSyncExternalStore` + sunucu snapshot'ı `null`: hydration'da **profilli** ziyaretçi için de önce profilsiz `<section ref>` bağlanır, sonra senkron yeniden render ref'i düşürür. IO ilk bildirimi ile React'in passive-effect görevi arasında sıra garantisi yok. | Kayıtlı profilli ziyaretçi, CTA kartı ilk ekranda görünen bir sayfayı sert yüklerse `cta_form_goruntulendi` yanlışlıkla 1 kez atılabilir → huni giriş adımı şişer. Kart çoğu sayfada fold altında olduğundan nadir. | düşük | IO callback'inde göndermeden önce `el.isConnected` + profil yokluğunu kontrol et ya da ref'i `profil === null && hydrated` koşuluna bağla. Merge'i engellemez; Rybbit'te `cta_form_goruntulendi / sira_girildi{cta}` oranı izlenmeli. | OLASI (yazılımcı da çalıştırarak doğrulamadı) |
| 6 | P2 `fa43392` (kapsam dışı kalan kardeş sorun) | `src/lib/analitik.ts:170-181`, `rybbit-identify.tsx` | Kuyruk yalnız `olay()` için; `kullaniciTanit()` script yokken hâlâ sessiz no-op. | OAuth / magic-link dönüşü (sert yükleme): identify effect'i script'ten önce koşar → o sayfa oturumunda kullanıcı eşlenmez; kuyruktan boşalan `sonuc_goruntulendi{girisli:evet}` anonim profile yazılır, P4'ün sunucu `giris_basarili{user_id}` olayıyla aynı profilde birleşmez. Regresyon değil (öncesinde de böyleydi) ama yeni huninin son adımını zayıflatır. | düşük-orta | Backlog: identify'ı da aynı kuyruğa al (tek son-değer slotu yeter). | OLASI |
| 7 | P4 `17046c8` | `src/lib/auth.ts:161-181`, `analitik-sunucu.ts:34-52` | Doğruluk tarafı temiz: `void` + `sunucuOlayi` içi `try/catch` + env yoksa erken `return` → giriş bozulamaz, unhandled rejection yok; `ctx.path` magic-link'te `/magic-link/verify`, Google'da `/callback/:id` (kaynakta doğrulandı); hook `queueAfterTransactionHook` içinde koşuyor. **Ama** olay sunucunun IP/UA'sıyla (`user-agent: node`) gidiyor. | Rybbit bot filtresi bu UA'yı düşürürse ya da olayı ayrı bir "sunucu oturumu"na yazarsa huni adımı tarayıcı oturumuyla bağlanmaz. `odeme_tamamlandi` de aynı yoldan; CTO zaten o 3 kaydın kaynağından şüpheli. | düşük | Secret'lar eklendikten sonra Bilal bir test girişiyle Rybbit'te `giris_basarili`'nin düştüğünü ve kullanıcı profiline bağlandığını doğrulasın; bağlanmıyorsa `/api/track` gövdesine istekten `user_agent` (ve gerekiyorsa `ip_address`) geçirilmesi ayrı iş. | OLASI |
| 8 | P4 `2af4a9f` | `src/lib/auth.ts:41-43` | Normalizasyon `trim().toLowerCase()`; Gmail nokta/`+etiket` eşlemesi yok. (Boşluklu adres zaten `z.email()`'de reddediliyor, `trim` fiilen no-op.) | `kurban+1@gmail.com`, `kurban+2@…` ile aynı kutuya soğuma aşılır; yine de IP limiti (5/60 sn) sınırlar. Bellek: her çağrıda budama → Map ≤ son 60 sn'deki adres sayısı, sızıntı yok. Yeniden başlatmada sıfırlanır, çoklu kopyada paylaşılmaz (tek konteyner — kabul). Google OAuth yolu `sendMagicLink`'e hiç girmiyor → etkilenmez. | düşük | Şimdilik kabul; istenirse gmail/googlemail için `+` sonrası ve noktalar atılabilir. | DOĞRULANDI |
| 9 | P1 `1a75f42` | `src/features/rehber/components/el-cizimi-sema.tsx:109-125` | `<figcaption>` artık `<figure>`'ün ortasında (iskelet/svg → figcaption → `<details>`); HTML içerik modeline göre figcaption ilk ya da son çocuk olmalı. | Tarayıcılar tolere eder, hydration'ı etkilemez; bazı doğrulayıcı/erişilebilirlik araçları uyarı verir. | düşük | `<details>`'i figcaption'dan **önce** koy ya da figcaption'ın içine al. | DOĞRULANDI |
| 10 | P3 `16c4508` | `src/lib/veri-kapsami.ts:6` | `VERI_YIL_SAYISI = 5` kolon kapsamıyla tutarlı (`sira2021…sira2025`). Ancak 23.610 programın yalnız **9.909**'unda beş yılın tamamı dolu (`sira2021` dolu: 11.450; `sira2025` dolu: 17.994). | "Her tercih için … trend analizi (son 5 yıl)" — 2025 tabanı olan programların ~%45'inde fiilen 5'ten az yıl var. Eski "4 yıl" da aynı ölçüde yaklaşık bir ifadeydi; regresyon değil. | düşük | İstenirse metin "son 5 yıla kadar" yapılabilir; Bilal kararı. | DOĞRULANDI (`sqlite3 -readonly`) |
| 11 | P1 `d9e125c` | `src/lib/rehber.ts:145` | `TARIH_BICIMI` yalnız biçim denetliyor: `2026-02-31` geçer (ay 13 ise `trTarih` build'i kırar, gün için kontrol yok). | Yazar `2026-09-31` yazarsa sitemap/JSON-LD'ye geçersiz tarih çıkar. Bugünkü 27 dosyada yok. | düşük | `Date.UTC` ile gün-ay geçerlilik kontrolü eklenebilir. | DOĞRULANDI |
### Özellikle bakılıp **temiz** bulunanlar
- **P1 parser:** P1'in `rehber.ts`'i hem kendi 24 dosyasında hem `gece/rehber-yazilari`'nın 27 dosyasında (7 `guncelleme`) hatasız; tüm `tarih`/`guncelleme` geçerli, `guncelleme ≥ tarih`. Tırnak değişikliği yalnız hedeflenen iki `aciklama`'yı etkiliyor (başka tırnak uçlu frontmatter değeri yok). Tablo sarmalayıcı: 8 tabloda aç/kapa sayıları eşit; içerikte ham `<table` HTML'i yok (dengesiz `</div>` riski yok). `<details open={hata || undefined}>` sunucu ve ilk istemci render'ında aynı → hydration uyuşmazlığı yok. `.catch` hem `import()` hem `render` hatasını yakalıyor, `iptal` korumalı, geçici `#d<id>` temizleniyor. Sitemap `lastmod` = `guncelleme ?? tarih`, `/rehber` = en büyük ISO string; yazı yoksa `undefined` (alan yazılmaz). `sayfaMetadata` `openGraph`'ı yayarak birleştiriyor → `modifiedTime` çıkar.
- **P2 hook/kuyruk:** callback ref, React 19 temizlik fonksiyonu döndürüyor; erken dönüşlerde `undefined` → React sökmede `ref(null)` çağırır, `!el` ile karşılanıyor. StrictMode çift mount: gözlemci `disconnect` + yeniden kurulur; `olayBirKez` sessionStorage ile tekilleşir. `setInterval` her iki çıkışta (`kuyruguBosalt`, deneme sınırı) temizleniyor; en çok 20 olay × 10 sn. `paket/layout.tsx` sunucu bileşeni, dinamik API yok; `useSearchParams` kullanılmıyor → `/paket` statikliği etkilenmez; `/paket` altında başka rota yok (olay yalnız `/paket`'te atılır). PII: olay özellikleri yalnız `kaynak`, `kod`, `status`, `yontem`, `girisli`, `sira_kovasi`; ham sıra/e-posta yok. `SihirbazBolumu` yalnız `/sonuc`'ta kullanılıyor → `sonuc_goruntulendi` adı doğru.
- **P3 risk:** `riskNotuMetni` doğrudan `riskHesapla` çıktısını tüketiyor → eşikler rozetle birebir (1,4× / ≥ sıra). Tadımlık yolu `riskHesapla(efektifSira, sira) ?? dilimdenRisk(dilim)` — `tadimlik-satiri.tsx:107` rozetiyle aynı ifade; `efektifSira` eksik/null → dilimden türetilir, `null` metne düşmez. `program` kolonu rozet tarafında zaten aynı şekilde okunuyor (yeni null riski yok). `rapor.ts`'te yalnız `riskNotuUret` gövdesi + import değişti; LLM şeması/prompt'u (5/13/6) aynı. `tadimlik-uret.ts` ve `api/soru` değişiklikleri yalnız prompt metni.
- **P4 env dosyası:** yeni yorum satırları (apostrof ve `→` içeren) `docker compose config` ile denendi → doğru ayrıştırılıyor; boş secret'lar `""` geliyor, kod `||` ile varsayılana düşüyor. `replyTo` alan adı Resend 6.18 tipinde var. Modül seviyesinde `throw` yok → `next build` env'siz kırılmaz. `kredi-hatirlatma.ts` yeni `throw`'u mevcut `try/catch` ile yutuyor.
- **Paketler arası sembol çakışması yok:** `giris_hata` (P2, istemci `OlayAdi`) ile `giris_basarili` (P4, `SunucuOlayAdi`) ayrı tipler; `trTarih` (P1) ile `KATALOG_TARIH_METNI` (P3) bağımsız; P2 `paket/layout.tsx` + P3 `paket/page.tsx` ayrık dosyalar.
- **AGENTS.md:** yeni badge/CTA/modal yok; "Yayın:" satırı düz metin; hero kutusu mevcut kutu; sihirbaz davranışı değişmedi → `SEMA` güncellemesi gerekmiyor; `/meraklisina`'da çıkan `<details>` metni tek kaynak `ariaLabel`'dan geliyor.
## 3. Bilal için önerilen merge sırası
1. **`gece/rehber-yazilari` + `gece/p1-rehber-altyapi`** birlikte (P1 olmadan `guncelleme` alanları görünmez; P1'in doğrulaması yeni frontmatter'larla sınandı — temiz).
2. **`gece/p2-olcum`** — ilk üç commit (`fa43392`, `cf19903`, `e1bb482`). `c055e82` (identify'dan e-posta/isim kaldırma) Bilal kararı; reddedilirse `git merge gece/p2-olcum~1`. Not: Rybbit'te önceden yazılmış e-posta/isim trait'leri kod değişikliğiyle silinmez.
3. **`gece/p3-vaat-dili`** — `git merge cae2aaa` (ilk iki commit) + istenirse `git cherry-pick 9d450c5` (YÖK logosu→metin; çakışmasız doğrulandı). **`52520ac`'yi Armut rakamı teyit edilmeden alma** (bulgu #1). Deploy sonrası prod'da tadımlık havuzu yeni prompt'la yeniden üretilmeli (bulgu #2).
4. **`gece/p4-eposta`** — en son, P2 merge edildikten sonra. Önce lokal docker testi (şartname §5: `EPOSTA_KONSOL_FALLBACK=1` ile link konsolda, 60 sn içinde ikinci istek link üretmez; bayraksız/anahtarsız → formda hata). Gitea'da `RESEND_API_KEY` + `EMAIL_FROM` secret'larının dolu olduğunu teyit et. `ea3ba5f` (ci.yaml) + `RYBBIT_*` secret'ları eklenmeden `giris_basarili` prod'da no-op kalır (zararsız).
5. Push öncesi AGENTS.md kuralı: birleşik HEAD temiz worktree'de `pnpm build`.