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

> **Review type:** plan-review (re-review)
> **Previous review:** `review-musteri-portali-plan-2026-07-31.md` (REJECTED, 7 bulgu)
> **Upstream document(s) read:** spec (S §5.2.1/§5.7/§12/§13 güncel halleri okundu) · brainstorm K-33 satırı
> **Other cross-referenced documents / kod:** `database/migrations/2026_07_26_000100_create_audit_tables.php:275–300` (**emsal fonksiyon gövdesi okundu**) · index P-9/§3/§6.1 · chunk2 §1.2 · chunk3 §1.3/§5 · chunk4 §1.1/§2/§4/§6 · `docs/issues.md:41` (düzeltilmiş hali) · AD-09 95–101
> **Stage locks in effect:** Spec kriterleri (M-1…M-47) ve brainstorm kararları (K-1…K-33) kilitli.

---

## Kapanış doğrulaması

| # | Doğrulama |
|---|---|
| F-P1 | ✓ **Mekanizma doğru kuruldu** — aşağıda soru 1'in tam cevabı. ⚠ İki test sorunu kaldı → R2P-1, R2P-2 |
| F-P2 | ✓ Kısmen — `min()` kaldırıldı, `sellerMax ?? globalMax` doğru; spec §5.2.1 ve U-2 geri alma **doğrulandı** (AD-09'un revize metnine birebir atıf); `docs/issues.md:41` düzeltmesi **doğrulandı** (düzeltme notu ile). ⚠ Üçüncü dalın kendisi yeni sorun → R2P-3 |
| F-P3 | ✓ Kapandı. Üç okuyucu tablo halinde chunk2'de: `PanelAccess` → enrolments, `CustomerUser` ilişkisi, `TrainingParticipantPolicy` → `TrainingEnrolmentPolicy` (+ `AuthServiceProvider` kaydı; eski silinmez); model kalır-okunmaz; geçiş testi (`musteri`) var |
| F-P4 | ✓ Kapandı — soru 3'ün cevabı aşağıda: K-33 zinciri dört katmanda tutarlı |
| F-P5 | ✓ Kapandı. `app/Support/Business/`'te yerinde düzeltme, üç caller sayılmış, kaynak AD-09:101 + egitmen-spec:417 |
| F-P6 | ✓ Kapandı. `Office\TrainingCostForSales`: maliyet planlamadan, salt okunur, kâr hesabı, **"maliyet yazan action yok" reverse-grep ile ölçülür** — M-36'nın doğru karşılığı |
| F-P7 | ✓ Kapandı (bir kuyrukla — Notlar): dört chunk'ta da audit testi var; chunk1 `audit_access` (TCKN açma) dahil, chunk4 "audit yazılamıyorsa işlem de olmaz" dahil |

## Sorulara cevaplar

**1 · P-9 yeterli mi? Evet — mekanizmanın kendisi sağlam.** Emsal fonksiyon gövdesi okundu (`audit_read_all`, satır 281–287): `current_setting(..., true)` + yalnız tam eşleşme açar; migration'ın kendi yorumu *"NULL, '' (COMMIT'in bıraktığı) ve 'off' hepsi kapalı"* diyor (B-5a) — `app.is_office()` aynı deseni kuruyor: GUC kurulmamışsa `v` NULL, `NULL = 'office'` → NULL → policy'de false, **varsayılan kapalı** ✓. `STABLE` emsalle aynı ✓. `SET LOCAL` tenant listesiyle aynı transaction'da (index §6.1) — AD-04'e uygun ✓. Migration 7.5, policy'den önce ✓. Yardımcı tablolar policy'li + denormalize `tenant_id` (RLS içinde `EXISTS` koşturmama gerekçesi doğru) ✓. `TenantPolicy`'nin FOR ALL deseninde `WITH CHECK` yazılmadığında `USING` ifadesine düşer — müşteri oturumu `tenant_id IS NULL` satır **ekleyemez** de; yeniden yazılan policy aynı deseni korumalı (Notlar'a yazıldı). Gözden kaçan iki şey mekanizmada değil **testlerde** — R2P-1 ve R2P-2.

**2 · Üç sonuçlu semantik — ilk iki dal birebir, üçüncü dal upstream'siz.** → R2P-3.

**3 · K-33 zinciri tutarlı.** Brainstorm K-33 → spec §5.7 → M-46/M-47 → chunk4: `valid_until` kolonu `SendQuote`'ta yazılır (varsayılan admin ayarı) ✓; **`expired` statü değil, türetim** (`OverdueQuotes`) ✓; statüye yalnız `cancelled` eklendi ve `declined`/`cancelled` ayrımı gerekçeli ✓; zamanlanmış komut yalnız hatırlatma, **statüye dokunmaz** ve `ExpireInstructorOffers`'ın neden emsal olmadığı yazılı ✓; kalemler `held` kalır, sayaçta işaretli ✓. "Sistem kendiliğinden karar vermez" çizgisi (çatı §151, K-10, AD-21'in silme kuralı) dört katmanda aynı.

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

**R2P-1 · Güvenlik testinin cümlesi ters yazılmış.** Chunk3 §5 (satır 119): *"`app.is_office()` yokken policy'nin **açık kaldığını** gösteren test"*. Doğrusu tam tersi: GUC kurulmamışken policy **kapalı** kalmalı (fail-closed — emsalın B-5a kuralı, ve P-9'un bütün varlık sebebi). Bu cümleyi birebir uygulayan biri fail-open'ı **doğrulayan** bir test yazar ve o test, güvenlik hatasını yeşile boyar. Tek kelimelik düzeltme ("kapalı kaldığını") ama yönü güvenliğin kendisi.

**R2P-2 · Negatif test (b) RLS katmanında sağlanamaz — yanlış grupta.** Chunk3 §1.3: üç negatif senaryo *"üçü de `omurga-rls` grubunda"*. Ama (b) — *"ofis kullanıcısı kapsamı dışındaki satış talebini göremez"* — P-9 policy'sinin sağladığı bir şey değil: policy ofis oturumuna **tüm** `tenant_id IS NULL` satırları açar; K-30 kapsam süzgeci bilinçli olarak **query katmanındadır** (§2 `TrainingRequestQueue`, M-7). RLS testinde (b) ya her zaman düşer ya da yanlışlıkla policy'ye kapsam eklenerek "geçirilir" — ikisi de yanlış. (b) `satis`/`planlama` grubuna uygulama-katmanı testi olarak taşınmalı; `omurga-rls`'de kalacak olan (a) ve (c)'dir.

**R2P-3 · Üçüncü dalın ("genişletilmiş limit içi teklif yine onaya düşer") upstream'i yok — ve EGT-58'in tanım-anı yarısı hâlâ adressiz.** AD-09'un revizesi onayı **parametre tanımlama anına** bağlar: genişletme *denemesi* onay akışına düşer, mekanizma AD-23'ünkidir — yani satış yöneticisi %25 globalken satışçıya %30 tanımlarken onay doğar. Fiyatlama-spec §4.2 ise teklif-anı onayını yalnız **etkin tavan aşımına** bağlar. Chunk4'ün üçüncü dalı ikisinde de olmayan bir şey söylüyor: onaylanarak tanımlanmış genişletilmiş limitin **içinde** kalan teklif (aşım yok) her seferinde yine onaya düşer — patron istisnası her teklifte yeniden onaylatılır. Bu muhafazakâr bir tasarım tercihi olabilir ama plan icadıdır; ya Sets kararıyla spec'e girer ya üçüncü dal "limit geçerli, aşım yok → normal akış" olur. **İkinci yarı:** genişletmenin tanım anındaki onay akışının nerede kurulduğu (limit tanımlayan `UpdateBusinessSettings` yolu) planda hâlâ yok — `docs/issues.md:41` mekanizmanın sahibini *"Spec 2 / chunk5"* diye işaretliyor. Tanım-anı onayı bu dikeyin dışındaysa, chunk4'ün *"EGT-58'i kapatır"* iddiası daraltılmalı: bu chunk **teklif-anı eskalasyonunu** kurar; tanım-anı onayının kapanış adresi ayrıca yazılır.

## ❓ Open Questions

1. R2P-3'ün karar sorusu: genişletilmiş limitin içindeki teklif her seferinde onaya düşer mi (çifte onay), yoksa limit onayı tanım anında bir kez mi verilir? Sets'in AD-09 gerekçesi ("patron %30 dedi — yapamayacaklar mı?") ikinci okumaya daha yakın duruyor ama karar reviewer'ın değil.

## ⚠️ Upstream Findings

- Yok — spec §5.2.1/U-2 ve issues.md düzeltmeleri doğrulandı; heyet prompt kural 2 düzeltmesi bu oturumdan doğrulanamaz (prompt dışarıda yaşar), beyana not düşüldü.

## Notlar (blocker değil)

- Yeniden yazılan `training_requests` policy'sinde `WITH CHECK`'in `USING` ile aynı ifadeyi taşıdığı (ya da FOR ALL deseninde ona düştüğü) chunk3 §1.3'e tek cümleyle yazılsın — müşteri oturumunun `tenant_id IS NULL` satır **ekleyemediği** de (c) negatif testine bir INSERT senaryosu olarak girsin.
- Chunk3'ün audit test satırı yalnız `audit_writes` içeriyor; spec §7.3 mail gövdesi/ek okumasının loglandığını söylüyor (AD-21) — thread okumasının `audit_access` senaryosu chunk3 testlerine eklenmeli (F-P7'nin küçük kuyruğu).
- `quotes:send-validity-reminders` "sistem kendiliğinden veri silmez/karar vermez" kuralıyla uyumlu: yalnız bildirim üretiyor ✓.

## Verdict: ❌ REJECTED (dar — bir kelime, bir grup taşıma, bir karar sorusu)

Yedi bulgunun yedisi de kaynağıyla doğrulanarak kapandı; P-9 mekanizması emsal gövdesiyle birebir ve fail-closed, K-33 zinciri dört katmanda tutarlı, F-P2'nin zincirleme düzeltmesi (spec + U-2 geri alma + issues.md) örnek nitelikte. Kalan üç şey: **R2P-1** — fail-closed testinin cümlesi tam tersini söylüyor ve bu haliyle güvenlik hatasını yeşilleyen bir test ürettirir; **R2P-2** — negatif test (b) RLS'in sağlamadığı bir şeyi RLS grubunda arıyor; **R2P-3** — indirim eskalasyonunun üçüncü dalı upstream'siz ve EGT-58'in tanım-anı yarısının kapanış adresi belirsiz (Sets kararı gerekiyor). Üçü kapanınca plan implementasyona hazırdır.
