Files
kolaytercih/docs/gece-vardiyasi/2026-10-05/03-qa-dal-ve-kod-denetimi.md
2026-10-05 00:59:46 +03:00

15 KiB
Raw Blame History

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)

  1. 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.
  2. 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 maxKm var (H1). Ayrı bir soruda da hiç kaydetmeden "Kaydettim" dedi (H4).
  3. 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).
  4. Ç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ı.
  5. Test edilemeyen: tarayıcıdaki useChat davranışı (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: ANKARA ve maxKm: 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". evIl olmadan 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ğıran src/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 abort parçası, ardından finish; "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ız error parçasını hata sayıyor, abort parç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_ara var, profilde ucretUst yok.
  • Yer: kayıt tamamen modelin sözüne bırakılmış (route.ts:100-109 prompt 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:56 yalnız zaman aşımı sinyali).
  • Koddan çıkarım: istemci kopunca hemen GET /api/soru yapı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/soru 100 mesaj döndü, sonuncusu "qa dolgu 69"; veritabanındaki son mesaj "qa dolgu 90". /listem sunucu çı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ı) ve s-a-u-a… (12'lik pencere danışman mesajıyla başlıyor).
  • Yer: route.ts:230-235 ve :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.validate tanı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: -5 kabul ediliyor ("yıllık ücret ≤ -5 TL"), 0 sessizce yok sayılıyor (:418-423);
    • tur: "__proto__" / "constructor" fırlatıyor (:395, in denetimi).
  • 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 ama il_ozeti "Afyon" bulunamıyor (program_ara aynı girdiyi buluyor). program_ara universite:"ODTÜ" → 0 (kısaltma yalnız universite_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_profil ve report_locks tabloları yoktu; pnpm db:migrate 0004-0005'i uyguladı. Göç koşmadan /api/soru POST 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-71 koddan 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ız yokatlas.db okur; profil_guncelle app.db'ye yazar ve user tablosunda o userId olmalı (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. ReadableStream döner; text-delta, tool-input-available, tool-output-available parçaları okunur. Sistem prompt'u route.ts içinde dışa aktarılmamış (sistemPromptu, :57); eval için ya kopyalanmalı ya da dışa aktarılması istenmeli.
  • @/ yolları: tsx worktree'nin tsconfig.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=x ile OpenAI uyumlu küçük bir sahte sunucu (500, yarıda kesme, takılma, sonsuz araç çağrısı) hem betikte hem pnpm 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).
  • reports satırı qa-rapor-1 (mevcut bir lokal raporun kopyası).
  • İki danisman_profil satırı, ~31 chat_messages, ilgili credit_ledger / session / verification satırları.
  • 0004 ve 0005 göçleri uygulandı. data/app.db git'te izlenen bir dosya ve artık değişmiş görünüyor — commit edilmedi, edilmemeli.

Bilal'den istenen

  1. H3 için karar: onay mesajı tamamen bedelsiz mi kalsın, yoksa yalnız tek kelimelik "evet/hayır" mı bedelsiz olsun (2 dk).
  2. H1 + H2 + H4'ün bir sonraki tura düzeltme işi olarak alınmasına onay (1 dk).