15 KiB
03 — QA: dal doğrulaması, kod denetimi ve lokal duman testi
Dal gece/eval-jev · incelenen değişiklik 6564ec8~1..b935158 (danışman sohbeti AI SDK'ya taşındı, 11 araç, Jev çıktı denetimi) · 5 Ekim 2026 gecesi · QA mühendisi
Özet (5 satır)
- Dal derleniyor ve ana akış çalışıyor. typegen + tsc + lint + build hatasız; gerçek modelle 7 soru soruldu: araç çağrısı akışta görünüyor, kredi doğru düşüyor, cevap kaydı ekrandakiyle aynı, Jev satırı her cevapta logda.
- En önemli hata — profil kaydı kayboluyor: danışman iki tercihi aynı anda kaydedince biri siliniyor; ekranda "Kaydettim: Ankara, 300 km" yazıyor, veritabanında yalnız
maxKmvar (H1). Ayrı bir soruda da hiç kaydetmeden "Kaydettim" dedi (H4). - Kredi tarafında iki delik: sağlayıcı takılırsa 120 sn sonra yarım cevap "tamam" sayılıyor, kredi iade edilmiyor (H2); "evet. " yazan kullanıcı 0 kredisiyle tam cevap alıyor (H3).
- Çalışan korumalar: model hatası / kopma / boş cevapta kredi iadesi, araç döngüsü sınırı (4 adım), bozuk araç argümanı, SQL enjeksiyonu, yetkisiz profil alanı, girişsiz istek (401) — hepsi çalıştırılarak doğrulandı.
- Test edilemeyen: tarayıcıdaki
useChatdavranışı (kural gereği tarayıcı açılmadı); istemci bulguları "koddan çıkarım" diye işaretli.
1. Dal doğrulaması
| Adım | Sonuç |
|---|---|
pnpm exec next typegen |
geçti |
pnpm exec tsc --noEmit |
geçti, 0 hata |
pnpm lint (eslint + tasarım denetimi) |
0 hata. 1 eslint uyarısı: src/lib/ai/cagri.ts:1 kullanılmayan OpenAI importu (bu değişiklikten kalma). Tasarım denetimi 12 uyarı, hepsi eski dosyalarda (secimlerim-paneli.tsx, meraklisina-demo.tsx) |
pnpm build (tek build) |
geçti, 1935 statik sayfa. Uyarılar: (a) iki pnpm-lock.yaml yüzünden kök dizin tahmini — worktree'ye özgü; (b) Cal Sans için yedek font ölçüsü bulunamadı |
pnpm start |
çalışıyor ama "next start" does not work with "output: standalone" uyarısı veriyor (üretim node server.js kullanıyor) |
2. Hata listesi
Önem: kritik yok. Aksi yazılmadıkça hepsi çalıştırılarak doğrulandı.
Yüksek
H1 — Aynı adımda iki profil_guncelle çağrısı birbirini eziyor
- Adım: "Ben Ankara'da yaşıyorum ve evden en fazla 300 km uzakta okumak istiyorum." → danışman onay sordu → "evet".
- Beklenen: profilde
evIl: ANKARAvemaxKm: 300. - Gerçekleşen: model iki aracı aynı adımda çağırdı; veritabanında yalnız
{"maxKm":300}. Ekrandaki cevap: "Kaydettim: Yaşadığın il Ankara, evden en fazla 300 km".evIlolmadan km süzgeci hiç uygulanmıyor (araclar.ts:387), yani kayıt fiilen etkisiz. - Kanıt: araç çıktıları
{"profil":["Yaşadığı il: Ankara"]}ve{"profil":["Evden en fazla 300 km"]}— her biri yalnız kendi alanını görüyor;GET /api/soru/profil→{"profil":{"maxKm":300}}. - Yer:
src/lib/danisman-profil-db.ts:29-48(oku → değiştir → yaz, işlem/kilit yok); çağıransrc/lib/ai/araclar.ts:799.
H2 — Sağlayıcı takılınca (120 sn zaman aşımı) yarım cevap "tamamlandı" sayılıyor, kredi iade edilmiyor
- Adım: sahte sağlayıcı birkaç kelime yollayıp sustu.
- Beklenen: hata + kredi iadesi (kopma ve 500'de olduğu gibi).
- Gerçekleşen: 120,4 sn sonra akışta
abortparçası, ardındanfinish; "Bu cevap takılacak ve " tam cevap gibi kaydedildi, kredi 6 → 5 kaldı, iade satırı yok, Jev de bu yarım metni denetledi. - Yer:
src/lib/ai/sohbet-akisi.ts:71-85— döngü yalnızerrorparçasını hata sayıyor,abortparçasını saymıyor; süre:56. Metin hiç gelmeden takılırsa "Boş cevap" yoluna düşüp iade olur (koddan çıkarım).
H3 — "Evet" bedelsiz yolu kredisiz soru sormaya izin veriyor
- Adım: kredi 0; son danışman mesajı "…profiline kaydedeyim mi?" → "evet. Ege hukuk taban sırası kaç?" (33 karakter).
- Beklenen: 402 "Kredin bitti" ya da yalnız onayın işlenmesi.
- Gerçekleşen:
X-Kredi-Dusuldu: 0,program_araçağrıldı, 450 karakterlik tam cevap geldi; kredi 0 → 0. - Yer:
src/app/api/soru/route.ts:49-54(yalnız "evet…" ile başlamasına ve 40 karaktere bakıyor),:252-257(bedelsiz yolda bakiye kontrolü yok). İstemci 0 kredide göndermeyi engelliyor ama uç doğrudan çağrılabilir./api/soru'da hız sınırı da yok (koddan çıkarım: route'ta sınırlayıcı yok), dolayısıyla bu yol sınırsız tekrarlanabilir.
H4 — Danışman kaydetmeden "Kaydettim" diyor (bir kez gözlendi)
- Adım: "Vakıf üniversitesine yıllık en fazla 400 bin TL ödeyebilirim." → onay sorusu → "evet. Ege hukuk taban sırası kaç?"
- Beklenen:
profil_guncelle(ucretUst)çağrısı, sonra "Kaydettim". - Gerçekleşen: cevap "Kaydettim: Vakıfta yıllık ücret üst sınırı 400.000 TL" ile başlıyor; akışta yalnız
program_aravar, profildeucretUstyok. - Yer: kayıt tamamen modelin sözüne bırakılmış (
route.ts:100-109prompt kuralı); sunucu "Kaydettim" ile gerçek araç çağrısını karşılaştırmıyor. Eval setine girmeli.
Orta
M1 — İstemci koparsa sunucu üretmeye devam ediyor; istemci yanlış bilgi veriyor
- Doğrulanan: istek 1,2 sn'de kesildi; sunucu cevabı sonuna kadar üretti (1.927 karakter), kaydetti, kredi düşük kaldı. İptal sinyali modele iletilmiyor (
sohbet-akisi.ts:56yalnız zaman aşımı sinyali). - Koddan çıkarım: istemci kopunca hemen
GET /api/soruyapıyor (sohbet-client.tsx:322-341); cevap o an henüz kaydedilmediği için "Kredin harcandıysa otomatik iade edildi" diyor ve soruyu kutuya geri koyuyor. İade yok; kullanıcı yeniden gönderirse aynı soruya iki kredi öder.
M2 — 100 mesajı aşan geçmişte en yeni mesajlar görünmüyor
- Adım: test kullanıcısında 121 mesaj.
GET /api/soru100 mesaj döndü, sonuncusu "qa dolgu 69"; veritabanındaki son mesaj "qa dolgu 90"./listemsunucu çıktısında da aynı. - Yer:
src/app/api/soru/route.ts:176-177,src/features/rapor/rapor-queries.ts:53-54(eskiden yeniye sıralayıp ilk 100'ü alıyor). Bu değişiklikten eski, ama yeni istemcinin kopma sonrası toparlama mantığı (sohbet-client.tsx:330, "son mesaj danışmanın mı") buna dayanıyor. Paketli kullanıcı 60+30 krediyle bu sınıra varır.
M3 — Başarısız sorular geçmişte kalıyor, modele bozuk sıra gidiyor
- Hata/iade sonrası kullanıcı mesajı silinmiyor. Sahte sağlayıcı günlüğünde modele giden roller:
s-u-a-u-a-u-a-u-a-u-u-u-a-u(art arda üç kullanıcı mesajı) ves-a-u-a…(12'lik pencere danışman mesajıyla başlıyor). - Yer:
route.ts:230-235ve:359-365(pencere sayıyla kesiliyor),src/lib/credits.ts:114(mesaj kredi düşümüyle yazılıyor, iade edilince kalıyor).
M4 — sayiParse "300 km"yi 300.000 okuyor
profil_guncelle {alan:"maxKm", deger:"300 km"}→ "Değer kabul edilmedi". "km"nin "k"si "bin" eki sayılıyor.- Yer:
src/lib/ai/araclar.ts:771.
M5 — Araç argümanları şemaya göre doğrulanmıyor
jsonSchema(parameters)doğrulayıcısız (araclar.ts:861;inputSchema.validatetanımsız). Sonuçlar:- yanlış tip (
arama: 12345,il: 35) →TypeError→ modele "Araç çalışmadı; veriye ulaşılamadı" (yanıltıcı: veri var, argüman bozuk); - geçersiz
dilim→ süzgeç etiketi"dilim = undefined", sessizce Güvenli dilimi uygulanıyor (:436-442); ucret_ust: -5kabul ediliyor ("yıllık ücret ≤ -5 TL"),0sessizce yok sayılıyor (:418-423);tur: "__proto__"/"constructor"fırlatıyor (:395,indenetimi).
- yanlış tip (
- Hepsi yakalanıyor, cevap düşmüyor; ama model yanlış süzgeçle doğru görünen sonuç alabiliyor.
Düşük
- L1 — Türkçe karaktersiz yazım tutarsız.
program_ara il:"IZMIR"→ "İl tanınmadı";arama:"isletme"→ 0 sonuç;il_ozeti "ISTANBUL"çalışıyor amail_ozeti "Afyon"bulunamıyor (program_araaynı girdiyi buluyor).program_ara universite:"ODTÜ"→ 0 (kısaltma yalnızuniversite_profili'nde). Yer:araclar.ts:322-338, 380, 648. - L2 — Kısa/boş girdide yanlış kayıt dönüyor.
bolum_ozeti "a"ve"---"→ Abaza Dili ve Edebiyatı;bolum_istihdam ["a"]→ Acil Yardım;universite_profili "a"→ Abdullah Gül. Yer:araclar.ts:598-601, 623-626, 735-738(boş slug her şeyle "başlıyor"). - L3 — Kısaltma tablosunda bozuk satır:
dtu: "deu"hiçbir üniversiteyle eşleşmiyor (araclar.ts:732). - L4 — Uzun girdi araç sonucuna aynen yansıyor (20.000 karakter girdi → 20 KB sonuç). Yer:
araclar.ts:370, 628, 792. - L5 — LIKE jokerleri kaçışsız:
arama:"%"tüm programları eşliyor. Zararsız. SQL enjeksiyonu denendi (' OR 1=1 --, sıralama/dilim alanları) — geçmiyor, tüm değerler bağlı parametre. - L6 — Hata logu tam hata nesnesini basıyor (
sohbet-akisi.ts:107), üstteki yorumun tersine. Mesaj içerikleri[Object]olarak kapalı; adres, model adı, cevap başlıkları görünüyor. - L7 — Kayıtlı cevap baştaki boş satırlarla yazılıyor (araç öncesi boş metin parçası;
sohbet-akisi.ts:74-78). Ekranda fark yok. - L8 — Jev eşiği dar: anlamsız nötr cümle 0,42 aldı (eşik 0,50; raporlanan temiz üst sınır 0,37). Gerçek 7 cevabın 2'sine not eklendi (0,57 ve 0,62); ilkinde "rahat yerleşirsin" ifadesi vardı, yani haklı.
- L9 — Depodaki
data/app.dbşeması geride:danisman_profilvereport_lockstabloları yoktu;pnpm db:migrate0004-0005'i uyguladı. Göç koşmadan/api/soruPOST 500 verir (koddan çıkarım). Üretimde giriş betiği göçü koşuyor.
3. Doğrulanan, çalışan davranışlar
| Konu | Sonuç |
|---|---|
Girişsiz GET/POST/DELETE /api/soru, /api/soru/profil, sahte çerez |
401 |
Kritik sayfalar (/, /giris, /paket, /rehber, /universiteler, /universiteler/izmir, /bolum/hukuk, /universite/ege-universitesi, /meraklisina, /gizlilik, /kosullar, /sitemap.xml, /robots.txt) |
200; /listem girişsizde /giris?callback=%2Flistem yönlendirmesi; olmayan sayfa 404 |
| Listesiz kullanıcı | 409 NO_REPORT, kredi düşmüyor |
| Kredi düşümü | her soruda −1, defterde chat_message; onay "evet" → X-Kredi-Dusuldu: 0 |
| Kredi 0 | 402 INSUFFICIENT, mesaj yazılmıyor, kredi_bitti_at işaretleniyor |
Aynı clientMessageId |
409 DUPLICATE, kredi düşmüyor |
| Girdi doğrulama (boş, 2001 karakter, sayı tipinde mesaj, kimliksiz, dizi gövde) | 400 |
| Sağlayıcı 500 / 429 (3 deneme) | akışta hata parçası, kredi iade (refund) |
| Akış yarıda kopunca | yarım metin kaydedildi, kredi iade |
| Boş cevap | hata, kredi iade |
| Araç döngüsü | 4 adımda duruyor; 4. adımda tool_choice: "none" |
| Bozuk JSON'lu araç argümanı | tool-input-error, cevap sürüyor |
| Jev | her cevapta [soru] jev ihlal olasılığı: …; garanti dilli sahte cevapta 0,96 ve not hem ekranda hem kayıtta |
| Kayıt–ekran tutarlılığı | akıştan birleştirilen metin kayıtla aynı (not dahil) |
| Yetki | istek gövdesinden yalnız message ve clientMessageId okunuyor; liste, geçmiş, profil oturumdaki kullanıcıdan (koddan çıkarım + testte başka kullanıcı verisi dönmedi) |
profil_guncelle |
kapalı liste: saglik, userId, __proto__ reddedildi; serbest metin ve aralık dışı sayı reddedildi |
| Paketsiz kullanıcı | danışman kilitli 4-5. tercihleri "görmüyorum" dedi |
Gerçek model çağrısı: 13 adım (sınır 15). Sahte sağlayıcılı testler model harcamadı.
4. Test edilemeyenler
- Tarayıcıdaki sohbet arayüzü (araç etiketi satırı, toast metinleri, kopma sonrası toparlama, bakiye rozeti): AGENTS.md gereği tarayıcı açılmadı. M1'in istemci yarısı koddan çıkarım.
- Jev zaman aşımı ve anahtarsız yol: çalıştırılmadı;
jev.ts:62-71koddan okununca cevap olduğu gibi kalıyor. - Eşzamanlı çift istek (aynı kullanıcı, iki sekme): denenmedi.
- Paketli kullanıcı (24 satırın tamamı prompt'ta): denenmedi.
- Gerçek sağlayıcıda zaman aşımı: yalnız sahte sağlayıcıyla üretildi.
5. Araçlar ve sohbet akışı Next dışından nasıl koşulur
Engel: src/lib/katalog.ts, rehber.ts, danisman-profil-db.ts import "server-only" içeriyor; düz tsx altında paket fırlatıyor.
Çözüm: react-server koşulu — server-only bu koşulda boş modüle çözülür. Kod değişikliği gerekmez.
# çalışma dizini worktree kökü olmalı (data/yokatlas.db ve file:./data/app.db buna göre çözülür)
pnpm exec tsx --conditions=react-server betik.ts # yalnız araçlar
pnpm exec tsx --env-file=.env.local --conditions=react-server betik.ts # sohbet akışı (model + Jev anahtarı)
- Araçlar:
aracCalistirici(baglam)(ad, arg)—src/lib/ai/araclar.ts:812. Bağlam:{ userId, sira, tur, profil: {} }. 10 araç yalnızyokatlas.dbokur;profil_guncelleapp.db'ye yazar veusertablosunda ouserIdolmalı (yabancı anahtar). AI SDK sarmalayıcısıaracSeti(baglam)aynı gövdeleri kullanır. - Sohbet akışı:
danismanAkisi({ system, mesajlar, aracBaglami, onTamam, onYarim })—src/lib/ai/sohbet-akisi.ts:29.ReadableStreamdöner;text-delta,tool-input-available,tool-output-availableparçaları okunur. Sistem prompt'uroute.tsiçinde dışa aktarılmamış (sistemPromptu,:57); eval için ya kopyalanmalı ya da dışa aktarılması istenmeli. @/yolları:tsxworktree'nintsconfig.json'ını kendiliğinden kullanıyor; betik worktree dışında durabilir, içe aktarmalar mutlak yolla yapılır.- Model harcamadan hata yolu:
AI_BASE_URL=http://127.0.0.1:3940/v1 AI_API_KEY=xile OpenAI uyumlu küçük bir sahte sunucu (500, yarıda kesme, takılma, sonsuz araç çağrısı) hem betikte hempnpm start'ta çalışıyor. - Lokal sunucuda giriş:
RESEND_API_KEY= EPOSTA_KONSOL_FALLBACK=1 RYBBIT_HOST= BETTER_AUTH_URL=http://localhost:<port> PORT=<port> pnpm start→ giriş linki sunucu günlüğüne düşer, e-posta ve analitik dışarı çıkmaz.
Kullanılan betikler (commit edilmedi): scratchpad qa/arac-test.ts (118 vaka), qa/akis-test.ts, qa/mock.mjs, qa/sor.sh.
6. Açılan qa- kayıtları
Hepsi yalnız bu worktree'nin data/app.db kopyasında; ana deponun veritabanına ve canlıya yazılmadı.
- Kullanıcı
qa-gece-1005@qa.invalid(magic link ile, konsol modunda; e-posta gönderilmedi). Kredisi testte elle 1 ve 10 yapıldı. - Kullanıcı
qa-arac-kullanici/qa-arac@qa.invalid(araç betiği için elle eklendi). reportssatırıqa-rapor-1(mevcut bir lokal raporun kopyası).- İki
danisman_profilsatırı, ~31chat_messages, ilgilicredit_ledger/session/verificationsatırları. - 0004 ve 0005 göçleri uygulandı.
data/app.dbgit'te izlenen bir dosya ve artık değişmiş görünüyor — commit edilmedi, edilmemeli.
Bilal'den istenen
- H3 için karar: onay mesajı tamamen bedelsiz mi kalsın, yoksa yalnız tek kelimelik "evet/hayır" mı bedelsiz olsun (2 dk).
- H1 + H2 + H4'ün bir sonraki tura düzeltme işi olarak alınmasına onay (1 dk).