# Review — Müşteri portalı ve teklif dikeyi planı (round 3) — 2026-07-31

> **Review type:** plan-review (dar teyit turu)
> **Previous review:** `review-musteri-portali-plan-2026-07-31-r2.md` (REJECTED, 3 bulgu)
> **Upstream document(s) read:** spec M-48 (okundu) · brainstorm kilitli
> **Other cross-referenced documents / kod:** `app/Support/Approvals/ApprovalKind.php` (**enum + docblock okundu**) · `app/Queries/Office/PendingApprovals.php`, `ApprovalController` (varlık) · chunk3 §1.3/§5 · chunk4 §1.1/§2/§2.1/§3/§6/§7 · index P-9
> **Stage locks in effect:** Spec kriterleri (M-1…M-48) ve kararlar kilitli.

---

## Kapanış doğrulaması — dört soru

**1 · R2P-1 — evet, düzeltilmiş cümle fail-closed'u doğru tarif ediyor.** Chunk3 §5: *"`app.identity_domain` hiç kurulmamışken policy'nin KAPALI kaldığını gösteren test — `current_setting(..., true)` NULL döner, `app.is_office()` false verir, satır görünmez"* + eski hatanın neden tehlikeli olduğu notu. Mekanizma zinciri (NULL → false → görünmez) emsalin B-5a kuralıyla birebir.

**2 · R2P-2 — katman tablosu doğru ve mimariyle tutarlı.** (a)/(c) `omurga-rls`'te, (b) `satis`/`planlama` satırında ve gerekçesi yazılı (P-9 ofise tüm NULL satırları açar; K-30 kapsamı query katmanıdır). `WITH CHECK` cümlesi eklendi ve (c) INSERT senaryosuyla ölçülüyor (satır 53); thread okumasının `audit_access`'e düşmesi teste girdi (satır 130). Bu, RLS'in ne olduğu/ne olmadığı ayrımının doğru kurulmuş hali.

**3 · EGT-58'in iki yarısı tek chunk'ta — tutarsızlık üretmiyor, tersine deliği kapatıyor; iki kalıntıyla.** Üçüncü dal metinden tamamen çıkmış (grep boş) ve test senaryosu (c) artık tam tersini — *"onaylanmış geniş limitin içindeki teklif normal akıştan gider"* — ölçüyor; M-48 ile birebir. Dört senaryolu test (a–d) iki anı da kapsıyor. `pending_approval` + "onaylanana kadar etkin tavan eski değer" kuralı M-48'in "onaysız geniş limit etkin değildir" cümlesinin doğru karşılığı. İki onay yüzeyinin ayrımı da tutarlı: limit onayı yönetimsel kuyrukta (`66 Onaylar`), teklif onayı teklif akışında (`quote_approvals`, AD-14). Kalıntılar aşağıda R3P-1 ve R3P-3.

**4 · Kalan sapma taraması** → R3P-2 (test satırında eski semantik kalıntısı).

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

**R3P-1 · "`ApprovalKind`'a yeni tür eklenir" iddiası yanlış — tür ZATEN VAR ve bilerek konmuş.** `ApprovalKind.php:33`: `case DiscountLimit = 'discount_limit'` mevcut; docblock'u durumu açıkça yazıyor: *"`DiscountLimit` exists with nothing behind it today: the sales module is unwritten… the screen shows the tab with a zero."* (Proje haritası da `66`'nın indirim sekmesini "satış modülünü bekliyor, sıfırla duruyor" diye kaydetmiş.) Plan iki yerde "yeni tür eklenir" diyor (§2.1 ve satır 78) — doğrusu **mevcut tür doldurulur**. Tek kelime değil, bir iş adımı farkı: docblock aynı zamanda bu türün **izninin bilinçli olarak null** olduğunu söylüyor ("a kind whose module is unwritten") — tür devreye girerken o iznin tanımlanıp `66`'nın sekmesinin kime açılacağının da bu chunk'ta yazılması gerekir; plan bunu hiç anmıyor.

**R3P-2 · Unit test satırında eski semantiğin kalıntısı: "`min()` daraltması".** Chunk4 §6 ilk satır hâlâ `DiscountCeiling` testleri arasında "**`min()` daraltması**"nı sayıyor — oysa §2, `min()`'i "YANLIŞTIR" diye kaldırdı ve etkin tavan `sellerMax ?? globalMax` oldu. Bu test tarifini uygulayan biri kaldırılan davranışı geri yazar ya da neyi test edeceğini bilemez. Satır "etkin tavan `sellerMax ?? globalMax`; onaysız geniş limit etkin değil" olarak düzeltilmeli.

**R3P-3 · `SetSellerDiscountLimit`'in caller'ı yok ve backbone olarak da işaretlenmemiş — P-7 ihlali.** Action §2.1'de ve action tablosunda var; chunk4'ün HTTP/frontend bölümlerinde limit tanımlama ekranı yok. Limitin tanımlandığı yer tasarımda `61 Yonetici Tanimlar`'ın satışçı düzenleme draweridir ve proje haritası o parçayı "satışçı tarafı yok" diye açık işaretliyor. P-7'nin kendi kuralı: caller aynı chunk'ta, ya da backbone ise **kapanış adresi yazılır**. İkisi de yapılmamış — ya `61`'in satışçı draweri (limit alanıyla) chunk4 kapsamına girer, ya da kapanış adresi açıkça yazılır. (Onay tarafının caller'ı sorun değil: `PendingApprovals`/`ApprovalController` mevcut, sekme türle birlikte dolar.)

## ❓ Open Questions

Yok — R2P-3'ün karar sorusu Sets'in iki kararıyla kapandı; kararların plan/spec karşılıkları (M-48, §2.1, test (c)) birebir.

## ⚠️ Upstream Findings

Yok.

## Notlar (blocker değil)

- `user_discount_limits`'e eklenen kolonlar additive ✓ (P-8); mevcut kolon adı `max_percentage` zeminle uyumlu.
- EGT-58 test senaryosunun (d) maddesi ("aşım belirgin görünür") Sets'in "alert olarak gözükür, buna rağmen onaylanıyorsa onaylanır" kararının doğru ölçümü.

## Verdict: ❌ REJECTED (çok dar — üç kalıntı, üçü de tek oturumluk düzeltme)

Üç bulgunun üçü de doğru kapandı ve doğrulama kaynaktan yapıldı: fail-closed cümlesi düzeltilmiş, test katmanları doğru ayrılmış, EGT-58'in birleşik kurgusu M-48 ile tutarlı ve delik kapatan tasarım. Kalanlar: **R3P-1** — "yeni tür" iddiası koddaki gerçekle çelişiyor (tür var, izni bilinçli null; doldurma adımı yazılmalı), **R3P-2** — kaldırılan `min()` semantiği unit test satırında unutulmuş, **R3P-3** — `SetSellerDiscountLimit` caller'sız ve kapanış adressiz (P-7). Üçü kapanınca plan implementasyona hazırdır.
