All checks were successful
Deploy / deploy (push) Successful in 7m18s
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.
15 KiB
15 KiB
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/ieşleşen satırlar atlanmalı /trendOzetidetrendOzetiUret` benzeri deterministik üretilmeli. Yorum satırı düzeltilmeli. |
| 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 hemgece/rehber-yazilari'nın 27 dosyasında (7guncelleme) hatasız; tümtarih/guncellemegeçerli,guncelleme ≥ tarih. Tırnak değişikliği yalnız hedeflenen ikiaciklama'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<tableHTML'i yok (dengesiz</div>riski yok).<details open={hata || undefined}>sunucu ve ilk istemci render'ında aynı → hydration uyuşmazlığı yok..catchhemimport()hemrenderhatasını yakalıyor,iptalkorumalı, geçici#d<id>temizleniyor. Sitemaplastmod=guncelleme ?? tarih,/rehber= en büyük ISO string; yazı yoksaundefined(alan yazılmaz).sayfaMetadataopenGraph'ı 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ökmederef(null)çağırır,!elile karşılanıyor. StrictMode çift mount: gözlemcidisconnect+ yeniden kurulur;olayBirKezsessionStorage ile tekilleşir.setIntervalher iki çıkışta (kuyruguBosalt, deneme sınırı) temizleniyor; en çok 20 olay × 10 sn.paket/layout.tsxsunucu bileşeni, dinamik API yok;useSearchParamskullanılmıyor →/paketstatikliği etkilenmez;/paketaltında başka rota yok (olay yalnız/paket'te atılır). PII: olay özellikleri yalnızkaynak,kod,status,yontem,girisli,sira_kovasi; ham sıra/e-posta yok.SihirbazBolumuyalnız/sonuc'ta kullanılıyor →sonuc_goruntulendiadı doğru. - P3 risk:
riskNotuMetnidoğrudanriskHesaplaçıktısını tüketiyor → eşikler rozetle birebir (1,4× / ≥ sıra). Tadımlık yoluriskHesapla(efektifSira, sira) ?? dilimdenRisk(dilim)—tadimlik-satiri.tsx:107rozetiyle aynı ifade;efektifSiraeksik/null → dilimden türetilir,nullmetne düşmez.programkolonu rozet tarafında zaten aynı şekilde okunuyor (yeni null riski yok).rapor.ts'te yalnızriskNotuUretgövdesi + import değişti; LLM şeması/prompt'u (5/13/6) aynı.tadimlik-uret.tsveapi/sorudeğişiklikleri yalnız prompt metni. - P4 env dosyası: yeni yorum satırları (apostrof ve
→içeren)docker compose configile denendi → doğru ayrıştırılıyor; boş secret'lar""geliyor, kod||ile varsayılana düşüyor.replyToalan adı Resend 6.18 tipinde var. Modül seviyesindethrowyok →next buildenv'siz kırılmaz.kredi-hatirlatma.tsyenithrow'u mevcuttry/catchile yutuyor. - Paketler arası sembol çakışması yok:
giris_hata(P2, istemciOlayAdi) ilegiris_basarili(P4,SunucuOlayAdi) ayrı tipler;trTarih(P1) ileKATALOG_TARIH_METNI(P3) bağımsız; P2paket/layout.tsx+ P3paket/page.tsxayrı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 →
SEMAgüncellemesi gerekmiyor;/meraklisina'da çıkan<details>metni tek kaynakariaLabel'dan geliyor.
3. Bilal için önerilen merge sırası
gece/rehber-yazilari+gece/p1-rehber-altyapibirlikte (P1 olmadanguncellemealanları görünmez; P1'in doğrulaması yeni frontmatter'larla sınandı — temiz).gece/p2-olcum— ilk üç commit (fa43392,cf19903,e1bb482).c055e82(identify'dan e-posta/isim kaldırma) Bilal kararı; reddedilirsegit merge gece/p2-olcum~1. Not: Rybbit'te önceden yazılmış e-posta/isim trait'leri kod değişikliğiyle silinmez.gece/p3-vaat-dili—git merge cae2aaa(ilk iki commit) + istenirsegit 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).gece/p4-eposta— en son, P2 merge edildikten sonra. Önce lokal docker testi (şartname §5:EPOSTA_KONSOL_FALLBACK=1ile link konsolda, 60 sn içinde ikinci istek link üretmez; bayraksız/anahtarsız → formda hata). Gitea'daRESEND_API_KEY+EMAIL_FROMsecret'larının dolu olduğunu teyit et.ea3ba5f(ci.yaml) +RYBBIT_*secret'ları eklenmedengiris_basariliprod'da no-op kalır (zararsız).- Push öncesi AGENTS.md kuralı: birleşik HEAD temiz worktree'de
pnpm build.