docs(gece): QA dal doğrulaması, kod denetimi ve lokal duman testi raporu
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
157
docs/gece-vardiyasi/2026-10-05/03-qa-dal-ve-kod-denetimi.md
Normal file
157
docs/gece-vardiyasi/2026-10-05/03-qa-dal-ve-kod-denetimi.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user