# Re-Review — Chunk 5 (EGT-33) implementasyonu: bulgu kapanışları + F-2 kod karşılığı — 27.07.2026

> **Review type:** impl-re-review (heyet 145'in 4 bulgusunun kapanışı + E-1/E-2 ek kusurlarının doğrulaması)
> **Previous review:** `review-omurga-chunk5-impl-2026-07-27.md` (❌ REJECTED — F-1…F-4)
> **Doğrulama yöntemi:** `git diff efd3393..HEAD` dosya dosya okundu (model/deriver/authenticator/3 migration/anon-rules/dump script); doküman kapanışları güncel dosyalardan grep + okuma; beyan kanıt sayılmadı. CI kanıtı **TC #746 SUCCESS, 345 test**, sıfırdan kurulan veritabanıyla.
> **Stage locks in effect:** ⛔ F-2 kararı Sets'indir ve (b) şıkkı seçilmiştir (şifrele + kök anahtarlı arama hash'i); karar sorgulanmaz, işlenişi denetlenir. ⛔ Kabul edilmiş artık risk (çapraz-tenant hash eşitliği) Sets kabulüdür.

---

## Kapanış doğrulaması

| Bulgu | Doğrulama | Durum |
|---|---|---|
| **F-2** e-posta düz metindi, sapma kayıtsızdı | **Kod:** `customer_users.email` → `text` + `TenantEncrypted` cast (model:78); `email_hash char(64)` + `TenantKeyDeriver::lookupHash()` (HMAC-SHA256, HKDF `PURPOSE_LOOKUP`, **normalise ile** küçük harf/boşluk — citext'in kaybı telafi edilmiş, case testi güncel); unique `(tenant_id, email_hash)`; kapı imzası `(p_email_hash text)` ve gövdesi `WHERE email_hash = p_email_hash` (000300:110) — düz metin adres veritabanına hiç gitmiyor (authenticator hash'leyip çağırıyor); `email_hash` üzerinde tenant'sız indeks, gerekçesi yorumda. **Kayıt:** artık risk üç yerde (spec §8.1 satır 322 — adlandırılmış istisna + gerekçe; migration yorumu; `InheritedCriteriaTest:174` riskin kendisini assert eden test) + chunk4 §4.1'e "searchHash kuralının adlandırılmış tek istisnası" notu + chunk9 **S-3** çağıran-bazlı allowlist (`app/` altında dizge yalnız `CustomerUser.php` — grep ile bağımsız doğrulandı). Türetilmiş değer senkronu `keepDerivedValuesInStep`'te, `key_version` e-postayı da kapsıyor | ✅ KAPANDI |
| **F-1** kesin liste senkronsuz | chunk2 §2.8'e iki satır gerekçeli girdi (332-333); chunk5 §5.1 başlığı "çoğu tenant-kapsamlı; ⚠ işaretli iki tablo bağlamsız" (42) | ✅ KAPANDI |
| **F-3** hayalet sınıf adları | Migration yorumları gerçek zorlama adreslerine çevrildi (`IdentityDomain::requiresMfaForEveryone()` + panel `isRequired`; `AuthSettings`) | ✅ KAPANDI |
| **F-4** ayar tablosu şekli kayıtsız | chunk5 §5.3 anahtar/değer şeklini gerekçesiyle yazıyor | ✅ KAPANDI |
| Açık soru 4 (oturum yazma yükü) | chunk9 §9.8 kriter **6a**: katkı yük testinde rakamla raporlanacak (155) | ✅ |

## E-1 / E-2 doğrulaması

**E-1 (maskeleme kapsamı).** `anon-rules.sql`: backoffice kimliği, müşteri kimliği, üç oturum tablosu (`payload`, `ip_address`) ve dört token tablosu için kurallar yazılmış; ciphertext kolonlar `NULL` ile maskeleniyor (chunk4 `DecryptException` dersi yorumda); **`customer_users.email_hash` de `NULL`** (satır 84 — "çapraz-tenant korelasyona açık tek değer, temsil ettiği adresle birlikte gider" gerekçesiyle). `anonymize-dump.sh` örtü kontrolü "kurallar koştu mu"dan **ad bazlı etiketsiz-kolon taramasına** çevrilmiş; etiketsiz hassas ad varsa dump üretilmiyor ve kolonları sayıyor. Sağlam kapanış.

**E-2 (fonksiyon overload kalıntısı).** Doğrulandı: 000300 migration'ı artık üç kapının **bütün** overload'larını `pg_proc`'tan okuyup düşürerek başlıyor (`dropEveryOverloadOf`, imza `oid::regprocedure`'dan); tespiti yapan "tam olarak bir `authenticate_lookup`" assertion'ı `CustomerAuthenticationTest:30`'da. Kusuru CI'ın değil, bu tur **eklenen assertion'ın** yakalamış olması doğru refleks — kontrol önce yazılmış, kusuru o bulmuş.

## ✅ What works

- **F-2 kapanışı bulgunun ruhunu aşıyor:** karar açıkça verilmiş, sapma kapatılmış, kalan risk gizlenmek yerine **üç yerde yazılıp bir testte assert edilmiş** ("iki tenant'ta aynı hash" — riskin kendisi test ediliyor, ima edilmiyor), ve kabulün geçerlilik koşulu ("tek yoldan kullanıldığı sürece") S-3 ile mekanik zorlamaya bağlanmış.
- `lookupHash`'in HMAC olması ("adresler tahmin edilebilir; anahtarsız digest, şüphelenilen adresi deneyerek tersine çevrilir") doğru kripto muhakemesi; `normalise` paylaşımı citext kaybını kapatıyor.
- E-1'in iki katmanlı örtüsü (açık etiket + ad bazlı ağ) ve E-2'nin hem anlık düzeltmesi hem kalıcı assertion'ı — her iki kusur da "bir daha olamaz" biçiminde kapatılmış.

## ⚠️ Findings & Risks (in-scope for this review type)

**R2-1 (minör) — `email_hash` dump script'in ad-bazlı listesinde yok.** `national_id_hash` listede, `email_hash` değil. Kolon bugün etiketli (satır 84), yani bugün açık yok; ama ad-bazlı ağın varlık sebebi **gelecekte** kuralsız kalan kopyaları yakalamak — ve sistemdeki çapraz-tenant korelasyona açık tek değerin adı tam bu listeye ait. Bir satırlık ekleme.

**R2-2 (minör) — `email_hash` nullable, oysa `email` NOT NULL ve benzersizlik artık hash üzerinde.** PostgreSQL'de NULL'lar birbirine eşit sayılmaz: model yolunu atlayan bir yazma (seed, onarım scripti — S-16 sınıfı) NULL hash'li satır üretirse `(tenant_id, email_hash)` kısıtının tamamen dışına düşer ve aynı adres ikinci kez kaydedilebilir. Şema taze ve veri yokken `NOT NULL` bedava; model yolu zaten her zaman dolduruyor.

**R2-3 (minör) — E-2'nin genelleştirme dersi chunk9'a işlenmedi.** Sunum "S-9 envanteri ad bazlı olması yetmez, imza taşımalı; chunk9 turunda bakılmasını isterim" diyor — ama chunk9 S-9 hâlâ ad/liste dilinde ve devir kaydı yok. Kendi meta-kural (chunk9'da olmayan kuralın zorlaması yoktur) gereği bir satırlık kayıt şimdi düşülmeli: *"S-9 envanteri imza bazlıdır; `app` şemasındaki fonksiyonlar beklenen imza listesiyle birebir karşılaştırılır (E-2 dersi: imza değişimi eski overload'ı yaşatır)."*

## ❓ Open Questions (to be answered before next stage)

Dört soruya değerlendirme:

1. **Kök anahtarlı hash'in sözleşmesi:** Bugünkü üç katman (spec §8.1 "tek meşru okuyucu bootstrap kapısıdır + taramayla denetlenir" · deriver "used for exactly one thing" · S-3 mekanik allowlist) ikinci istisnanın eşiğini zaten yüksek tutuyor. Sorduğun cümle yine de eklenmeye değer — G-şartları emsalindeki gibi **duruma** bağlı yazılırsa: *"kök anahtarlı türetme yalnız 'kayıt tenant bilinmeden bulunmak zorunda' durumunda meşrudur; ikinci bir kök-anahtarlı PURPOSE eklemek spec değişikliğidir."* Adresi deriver yorumu değil, spec §8.1 veya S-3 — yorumlar sözleşme taşımaz (F-3'ün dersi).
2. **E-2'nin genel hâli chunk9'un işi** — imza-bazlı envanter karşılaştırması S-9'un doğal genişlemesi ve tarama sınıfının tam tanımı. Bu chunk'ın elle drop + exactly-one assertion'ı doğru yerel çözüm. Eksik olan yalnız devir kaydı (R2-3).
3. **E-1 ad listesi ayrımı doğru.** `name` listeye girmemeli: `customers.name` şirket adıdır ve maskeli kopyada işlev taşır; belirsiz adlar açık kuralla yönetiliyor ve örtü zaten iki katmanlı. Listeye girmesi gereken tek eksik `email_hash` (R2-1).
4. **E-posta arama kuralı şimdiden yazılmalı.** Kural yüzeyden önce konur; konmazsa ilk arama ekranı ya "çalışmıyor" diye geri gelir ya da birine düz-metin yardımcı kolon ekletme baskısı üretir — F-2'nin kapattığı şeyin arka kapıdan dönüşü. İçeriği iki cümledir: *e-postaya göre bulma tenant kapsamı içinde `email_hash` eşitliğiyle yapılır; kısmi/LIKE araması bu kolonda **yoktur ve olamaz** — bu, şifrelemenin bilinçli bedelidir.* Doğal adres chunk5 §5.1 (kolonun tanımlandığı yer) + arama yüzeyini doğuracak planlara devir notu. Autocomplete tarzı bir ihtiyaç doğarsa bu bir spec kararı olarak geri gelir, sessiz bir kolon olarak değil.

## Verdict: ✅ APPROVED

Dört bulgunun dördü de dosya üzerinde doğrulanarak kapandı; F-2'nin kod karşılığı karar-kayıt-zorlama üçlüsünü eksiksiz kuruyor ve iki ek kusur (E-1, E-2) kalıcı korumalarıyla birlikte kapatılmış. Kalan üç bulgu minördür — bir satırlık liste eklemesi (R2-1), bir kolon kısıtı (R2-2), bir devir kaydı (R2-3) — ve emsal (heyet 133) uyarınca yeni tur gerektirmeden kapatılabilirler; R2-2 şema dokunuşu içerdiği için chunk5 dalı kapanmadan işlenmeli. Bunlarla birlikte chunk 5 implementasyonu spec v1.11 ve plan kriterlerinin tamamını karşılıyor; **EGT-33 merge'e hazırdır**. Son karar kullanıcınındır.
