# Review — Ofis T4 Planı v2.1 — Re-review 3 — 2026-07-28

> **Review type:** plan-review (re-review)
> **Previous review:** `docs/reviews/review-ofis-t4-plan-2026-07-28-r2.md` (REJECTED, 8 + 1 upstream)
> **Upstream document(s) read:** `docs/arch/mimari-kararlar.md` AD-23 (U-1 revizyonu yerinde doğrulandı: Bileşenler bloğu artık üyelik = kişi/grup/seviye + "etkin kapsam dar olandır" satırı) · AD-09, AD-24 Ek 2, AD-05/08/11/15/21/26 · `egitmen-spec` §1–§4 (kilitli) · çatı §174/§178
> **Other cross-referenced code:** `app/Support/Tenancy/SessionScopeResolver.php` (tam okundu — Soru 2 için), `app/Actions/Office/ChangeOfficeAccountStatus.php` + `routes/office.php:201` (Soru 3 için), `app/Models/DocumentType.php:56`, `2026_07_26_001000` trigger'ları, `config/authorization.php`
> **Stage locks in effect:** `egitmen-spec` kilitli (heyet 158).

---

## Önceki bulguların kapanışı (R2: 8 + U-1)

| # | Durum | Kanıt |
|---|---|---|
| R2-1 | ⚠ Kısmen | Adım 3b eklendi, `min()` kuralı ve tablo var; **ama tablo kendi ⚠ paragrafıyla ve güncellenmiş AD-23'le çelişiyor** → R3-1 |
| R2-2 | ✓ Kapandı | `SessionScopeResolver:73` + `PermissionMatrix` envantere girdi (Adım 4 ⛔); kapsam kararı `ScopeResolver`'a taşınıyor; A-11b. Kalan boşluk: izinsiz-atanmış kullanıcının ve `.group`'un semantiği → R3-2 |
| R2-3 | ✓ Kapandı | İki satırlık uygulama tablosu (liste `groupsFor` / tek kayıt `covers`); `UserPolicy` üç metodu; A-13a |
| R2-4 | ✓ Kapandı | `groupsFor` `@throws LogicException` + gerekçe (Adım 3 ⛔); A-11 yeniden yazıldı. Adım 6'da bayat bir satır kalmış → R3-5 |
| R2-5 | ✓ Kapandı | Görev 6 Adım 3 ⛔: iki kontrol de muaf, gerekçe aile ayrımından; A-9a |
| R2-6 | ✓ Kapandı | Görev 8 Adım 1 ⛔: `deny_critical_*` trigger'ları iki yeni sahip tablosunda kurulur + testle kanıtlanır; A-16a |
| R2-7 | ✓ Kapandı | `DocumentType::SCOPE_INSTRUCTOR` (**'egitmen'**) sabitten; `SCOPE_OFFICE_USER` dahil; A-16b |
| R2-8 | ✓ Kapandı | Görev 7 Adım 2b: `instructor.ministry_notification` bu turda açılıyor, süre ayardan, görev-açar-bildirim-göndermez; A-15a. Ayrılış sınırı yazılmış ama tetikleyicisi bu turda canlı → R3-4; assignee izni adsız → R3-3 |
| U-1 | ✓ Kapandı | AD-23 Bileşenler bloğu revize: üyelik = (kişi, grup, **seviye**), rol satırı kaldırıldı, revizyon gerekçesi kararda; "etkin kapsam dar olandır" AD'ye girdi (Sets onayı) |

---

## Upstream Coverage Matrix (v2.1 farkları; değişmeyen ✓ satırlar r2 matrisiyle aynı)

| Upstream § / madde | Durum | Kanıt |
|---|---|---|
| AD-23 — "etkin kapsam dar olandır; ⚠ `all` yalnız izin ekiyle gelir ve **ağaçtaki yerden bağımsızdır**" | ❌ | Planın Adım 3b tablosu 4. ve 5. satırlarında `all`'u seviyeyle/üyeliksizlikle daraltıyor — AD'nin kendi cümlesine aykırı → R3-1 |
| AD-23 — üyelik seviyesi, Bileşenler tutarlılığı | ✓ | U-1 kapandı; plan K-F ile uyumlu |
| AD-23 güvenlik kuralı 1 + muafiyet (alt-küme dahil) | ✓ | Görev 6 Adım 3 ⛔, A-9/A-9a |
| Y-2016 md.13/j — "kadrodaki **her** değişiklik 15 günde bildirilir" (`egitmen-spec` §1.2, kilitli) | ⚠ | İşe alım tetikleyicisi bu turda kuruldu (Adım 2b ✓); **ayrılış tetikleyicisi de bu turda canlı** (`ChangeOfficeAccountStatus` + `office.users.status` rotası mevcut — eğitmen kullanıcısı bugün pasifleştirilebilir) ama görev açılmıyor → R3-4 |
| AD-24 Ek 2 + AD-11 trigger'lar | ✓ | Görev 8 Adım 1 ⛔, A-16a |
| AD-05/21 — hassas veri | ✓ | Görev 7 Adım 5, A-21 |
| A-12 "migration sonrası aynı küme" güvencesi | ⚠ | Adım 3b tablosunun "üyeliği yok → own" satırı bu güvenceyle çelişiyor (mevcut kullanıcıların hiç üyeliği yok) → R3-1; izinsiz-atanmış müşteri kapsamı tanımsız → R3-2 |
| K-A…K-F | ✓ | r2 ile aynı; K-F bileşimi Adım 3b'de (R3-1 düzeltmesi şartıyla) |

---

## ✅ What works

- **U-1 örnek kapanış:** AD-23'ün Bileşenler bloğu yalnız düzeltilmemiş, revizyonun gerekçesi ("rol yetki seti olsaydı üyelik vermek yetki vermek olurdu") kararın içine yazılmış — AD disiplinine uygun.
- R2-2'nin çözümündeki ayrım doğru ve iyi ifade edilmiş: `allows('customer.view_all')` bir **kapsam kararıdır**, gate'in "bir şekilde görebiliyor mu" sorusuyla yakalanamaz — `ScopeResolver`'a taşınması doğru adres.
- R2-5/R2-6/R2-7 kapanışları gerekçeli ve test kriterine bağlanmış (A-9a, A-16a, A-16b); R2-7'nin "boş dönerdi ve hata vermezdi" tespiti, hatanın sınıfını doğru adlandırıyor.
- R2-8'in işe alım tarafı tam istenen şekilde: görev türü + ayar + "görev açar, bildirim göndermez" sınırı (AD-15'e atıfla).

## ⚠️ Findings & Risks (in-scope)

**R3-1 — Adım 3b tablosu kendi paragrafıyla VE güncellenmiş AD-23'le çelişiyor; tablodaki okuma uygulanırsa `.all` ölür ve A-12 çöker.** Tablonun 4. satırı `group_manager` + `user.view.all` → **`group`** ("seviye daraltır"), 5. satırı "üyeliği yok + herhangi → **`own`**" diyor. Hemen altındaki ⚠ paragraf ve AD-23'ün yeni Bileşenler satırı ise tam tersini söylüyor: *"`all` yalnız izin ekiyle gelir ve ağaçtaki yerden bağımsızdır — onu taşıyan kişi ağaçtaki yerinden bağımsız olarak her şeyi görür."* İkisi aynı anda doğru olamaz. Tablodaki okuma ayrıca iki şeyi kırar: (a) hiçbir seviyenin tavanı `all` olmadığından (`staff`→own, `gm`/`director`→group, üyeliksiz→own) `.all` **hiçbir kullanıcı için asla etkinleşemez** — ölü kapsam; (b) migration mevcut sahipleri `.all`'a taşıyor ama mevcut kullanıcıların **hiç üyeliği yok** (gruplar bu turda doğuyor) — tabloya göre hepsi `own`'a düşer ve A-12'nin "aynı kümeyi görür" güvencesi imkânsızlaşır. Doğru kural (Soru 1'in cevabı): **seviye tavanı yalnız ağaca referans veren `group` ekini daraltır; `all` seviyeden ve üyelikten bağımsızdır.** Tablo şöyle düzelmeli: `gm`+`.all` → `all`; üyeliksiz+`.all` → `all`; üyeliksiz+`.group` → `own` (ağaçta yeri yok). `staff`+`.all` da daraltılmamalı — `.all` zaten devir kuralı (A-9) korumasında bilinçli verilen bir yetkidir.

**R3-2 — Yeni `SessionScopeResolver` tarifi iki durumu tanımsız bırakıyor.** (a) **İzinsiz-atanmış kullanıcı:** bugünkü kod izin taşımayan herkese `office_user_customers` atamalarını veriyor (`SessionScopeResolver:82-86` — atama **default'tur**, izin değil; dosyanın kendi yorumu "otherwise works from an assigned list"). Migration yalnız `customer.view_all` sahiplerini `.all`'a taşıyor; bugün izin taşımadan atanmış listesiyle çalışan satışçılar yeni üç-kademeli tarifede hangi kademeye düşer? Tarif ("`.own` ise kendi atanmış müşterileri") izin **taşıyanları** sayıyor; taşımayanın davranışı yazılmamış — "izin yoksa boş" diye uygulanırsa bu kullanıcıların müşteri kapsamı **sessizce sıfırlanır** (A-11b'nin öbür ucu). İzinsiz default'un korunacağı (atamalar) açıkça yazılmalı. (b) **`customer.view.group`'un anlamı:** müşteri→grup zorunlu ataması ertelendi (kapanış tablosu 5) — "grup kapsamı" neye göre çözülür: kapsamımdaki kullanıcılara atanmış müşterilerin birleşimi mi, (henüz var olmayan) grup-müşteri bağı mı? Tanımsızken bu izin `PermissionChecklist`'te verilebilir olacak ve boş küme dönecek. Ya semantiği pinlenmeli ya `.group` sürümü bu turda üretilmemeli/verilememeli.

**R3-3 — Adım 2b'nin assignee kuralı izin adı vermiyor.** `user.permissions_pending` için kural nettir (`user.assign_permissions` taşıyan + kapsayan). `instructor.ministry_notification` için "eğitmen dikeyinde bildirimi yapan yetkili (kapsam çözümleyicisi)" deniyor ama `groupsFor`/`covers` hangi **izin adıyla** çağrılacak? Böyle bir izin yok, mevcutlardan hangisinin vekil olacağı yazılmamış — geliştirici seçim yapmak zorunda kalır ve K-E fallback'inin ne zaman devreye gireceği belirsizleşir. İzin adı (mevcut sözlükten veya bu turda doğacak bir ad) pinlenmeli.

**R3-4 — R2-8'in kendi ilkesi ayrılış tarafına uygulanmamış: ayrılışın tetikleyicisi de bu turda canlı.** Plan ayrılışı "kullanıcı çıkışı akışıyla müşteri/satış turuna" bırakıyor. Ama pasifleştirme **bu turdan önce zaten üretimde**: `ChangeOfficeAccountStatus` + `office.users.status` rotası mevcut ve bu turda eğitmen kullanıcıları doğduğu anda pasifleştirilebilir hale geliyorlar. Y-2016 md.13/j *"her türlü değişiklik"* der (Soru 3'ün cevabı: bu ayrım mevzuat açısından savunulamaz); işe alım için "tetikleyicisi canlıysa ertelenemez" diyen gerekçe, ayrılış için de aynen geçerli. Maliyet küçüktür: pasifleştirilen kullanıcının `instructors` kaydı varsa aynı `kind` ile görev açan bir koşul. Ya bu eklenmeli ya riski (bu turda pasifleştirilen eğitmenler için hatırlatmasızlık) Sets açıkça kabul etmeli.

**R3-5 — Adım 6'da bayat test satırı: "kapsamsız isimde `null`".** Adım 3 ⛔ ve A-11 artık **istisna** diyor; `ScopeResolverTest` tarifi hâlâ v2.0'ın `null` davranışını kanıtlıyor. Satır "kapsamsız isimde istisna" olarak düzeltilmeli — mevcut haliyle test, planın yasakladığı davranışı yeşile boyar.

*Not (bulgu değil):* AD-23'ün revizyon satırı gerekçe için `docs/plans/ofis-t4-plan.md K-D`'ye işaret ediyor — upstream'in downstream'e referansı, plan arşivlendiğinde kırılır; ileride K-D'nin özünü AD'ye taşımak daha dayanıklı olur.

## ❓ Open Questions

1. R3-2b'nin karar tarafı: `customer.view.group` bu turda **verilebilir** olmalı mı, yoksa müşteri→grup ataması gelene kadar üretilmemeli mi? (Sets kararı.)
2. R3-4'ün karar tarafı: ayrılış görevi bu turda mı, riski kabul mü? (Sets kararı.)

## Sorulara cevaplar (talep edilen dört nokta)

1. **Bileşim tablosu:** `all`'un seviyeden bağımsız olması **doğru okumadır** ve zaten hem planın ⚠ paragrafının hem güncellenmiş AD-23'ün söylediğidir; yanlış olan tablonun 4-5. satırları. Strict-min uygulanırsa `.all` hiçbir kullanıcı için etkinleşemez (hiçbir seviyenin tavanı `all` değil) ve üyeliksiz mevcut kullanıcılar `own`'a düşerek A-12'yi imkânsızlaştırır. `staff` + `.all` da daraltılmamalı — `.all` devir kuralının koruması altında bilinçli verilen bir yetkidir (R3-1).
2. **`SessionScopeResolver` çözümü:** Taşımanın kendisi müşteri atama mantığını **bozmaz** — `office_user_customers` tablosu ve atama akışı aynen duruyor, `.own` onun üstünden okunur. Bozulma riski iki tanımsız noktada: izin taşımayan-atanmış kullanıcının default davranışı ve `customer.view.group`'un bağsız semantiği (R3-2).
3. **R2-8'in sınırı:** Kabul edilebilir **değil** — md.13/j "her türlü değişiklik" der ve ayrılışın tetikleyicisi (mevcut pasifleştirme akışı) bu turda eğitmenler doğar doğmaz canlı. İşe alım gerekçesinin simetriği ayrılışa da uygulanmalı ya da risk açıkça kabul edilmeli (R3-4).
4. **Kalan 8 ertelemenin dürüstlüğü:** 1, 2, 4, 6, 7, 8 dürüst — tetikleyicileri bu turda yok (eğitim kaydı, aday listesi, onay akışı yok; devir sihirbazının konusu müşteri taşıyan kullanıcı çıkışıdır ve pasifleştirme bu turun yeniliği değil). 3 (kategori tarifesi) savunulabilir — ücret bu turda hesaba girmiyor, boş ücret uyarısı köprü. Sorunlu olan **5**: müşteri→grup ataması ertelenirken bu turda `customer.view.group` izni doğuyor ve semantiği o ertelenen bağa muhtaç (R3-2b). Tablo dışında kalan bir kalem de R3-4'tür (ayrılış bildirimi — tabloya hiç yazılmamış).

## ⚠️ Upstream Findings

Yok — U-1 kapandı; AD-23'ün yeni hükmü tutarlı. (Plan-referansı notu yukarıda, bulgu değil.)

## Verdict: ❌ REJECTED

9 bulgunun 9'u da gerçek işle karşılanmış; U-1 dahil kapanış kalitesi yüksek. Ancak R2-1'in kapanışı kendi içinde çelişkili doğmuş: Adım 3b'nin tablosu, planın kendi paragrafına ve AD-23'ün yeni hükmüne aykırı ve bu haliyle uygulanırsa `.all` kapsamını öldürüp A-12'yi çökertir (R3-1) — tek başına blocker. Yanına dört dar bulgu: izinsiz-atanmış müşteri kapsamı + `customer.view.group` semantiği (R3-2), bildirim görevinin izin adı (R3-3), ayrılış tetikleyicisi (R3-4), bayat test satırı (R3-5). Beşi de yerelleştirilmiş düzeltmeler; bir sonraki sürüm kısa olmalı.
