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

158 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.
# 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. <yeni soru>" 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.
```sh
# ç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).