# Review — Ofis T4 Planı v2.0 — Re-review 2 — 2026-07-28

> **Review type:** plan-review (re-review)
> **Previous review:** `docs/reviews/review-ofis-t4-plan-2026-07-28.md` (REJECTED, 16 bulgu)
> **Upstream document(s) read:** `docs/arch/mimari-kararlar.md` AD-23 (28.07.2026 güncel — ağaç düzeltmesi + üyelik seviyesi paragrafı yerinde doğrulandı), AD-09, AD-24 Ek 2, AD-05/08/11/21/26 · `docs/specs/egitmen-spec.md` §1–§4 (kilitli) · `cati-dokumani.md` §174/§178 · tasarım 61/63 (r1'de okundu)
> **Other cross-referenced documents / code:** planın "Doğrulanmış mevcut durum" tablosundaki 13 iddianın satır referansları tek tek dosyadan kontrol edildi; ek olarak `app/Support/Tenancy/SessionScopeResolver.php`, `app/Support/Authorization/PermissionMatrix.php`, `app/Queries/Office/*` (dizin), `app/Models/DocumentType.php`, `database/seeders/LocalOfficeAccountSeeder.php`, `2026_07_26_001000_create_sensitive_data_tables.php` (kritik belge trigger'ları)
> **Stage locks in effect:** `egitmen-spec` kilitli (heyet 158).

---

## Önceki bulguların kapanışı (16/16)

| # | Durum | Kanıt |
|---|---|---|
| F-1 | ✓ Kapandı | Migration kaldırıldı; plan 98 ⛔ "purpose migration'ı YOKTUR" + `2026_07_28_040100:37` referansı doğru |
| F-2 | ✓ Kapandı | Liste mevcut sözlükten (plan 207-219); `quote.view`/`training.view` çıkarıldı, ofis guard filtresi yazıldı — `config/authorization.php:25-80` ile birebir doğrulandı |
| F-3 | ✓ Kapandı | `user.create` kapsamlı (`.group/.all`, `.own` üretilmez) — çelişki kalktı (plan 213) |
| F-4 | ⚠ Kısmen | `ScopedGate` + `OfficeUsers` filtresi doğru kurgu; ama çağıran envanteri eksik (→ R2-2) ve nesne-seviyesi rotalar kapsamsız (→ R2-3) |
| F-5 | ✓ Kapandı | `role_id` kaldırıldı, K-F seviye modeli; `LocalOfficeAccountSeeder:80-85` doğru okunmuş. Yeni belirsizlik → R2-1 |
| F-6 | ✓ Kapandı | Hassas alanlar `instructors` üzerinde encrypted cast; `customer_documents` yasağı gerekçesiyle yazılı (plan 517-521) |
| F-7 | ✓ Kapandı | Görev 8: `instructor_documents` + `office_user_documents`, `document_type_scopes` bağı (→ yalnız R2-6/R2-7 kalıntıları) |
| F-8 | ✓ Kapandı | Görev 9: `/yonetim/gorevlerim`, rozet, yükleme yolu; `MyTasks` ekrana bağlı |
| F-9 | ✓ Kapandı | Fan-out kişi başına satır; `CompleteTask` (insan, yalnız kendisi) / `ResolveTasks` (olay, kardeşler) — çelişki çözüldü (plan 405-412) |
| F-10 | ✓ Kapandı | K-E: boş küme → backoffice, kalıcı mod; `tasks.assignee` polimorfik (plan 388, 444) |
| F-11 | ✓ Kapandı | Kapı izne taşındı: `office_user.create` / `office_user.assign_permissions` (plan 336-343; `config/authorization.php:184-187` doğru) |
| F-12 | ✓ Kapandı | Öncül düzeltildi ("yarısı zaten var", `AssignOfficePermissions:51-58`); yalnız eksik iki parça ekleniyor (plan 448-457). Metin hassasiyeti → R2-5 |
| F-13 | ✓ Kapandı | Üç kalem kapanış adresi tablosunda (5, 6, 7) + "görev ≠ onay" notu |
| F-14 | ✓ Kapandı | İki kuruluş sınırı ayara taşındı (Görev 7 Adım 2); `last_taught_on` adresli; CTU/CSS `document_type_scopes`'tan; tarife adresli. Kalem 4'ün ertelemesi → R2-8 |
| F-15 | ✓ Kapandı | `?NewInstructorFile` imzada; koşullu bölüm + sorumluluk sınırı yazılı (plan 329, 362) |
| F-16 | ✓ Kapandı | `invited` mevcut `HasAccountStatus` yoluna (`User.php:44,93`, `EnsureAccountIsActive:30` doğrulandı); şablon + TTL seeder'ları planlı (Görev 3 Adım 2-3, Görev 11 Adım 2) |

"Doğrulanmış mevcut durum" tablosunun 13 satırı da dosyadan kontrol edildi — **hepsi doğru** (`uploadable_by` `2026_08_04_000200:122` ✓, `document_type_scopes` :145-156 ✓, `OfficeNavigation:47` ✓, `Settings.php` ✓, `notification_templates` ✓). v1'in kök hatası gerçekten kapanmış; kalan bulgular yeni içeriğin kendisinde.

---

## Upstream Coverage Matrix (v2.0 için güncel)

| Upstream § / madde | Durum | Kanıt |
|---|---|---|
| AD-23 — tek ağaç + üyelik; 28.07 düzeltmesi (Planlama Dir. / Eğitim Dir.) | ✓ | Görev 1; AD-23 ağacı güncellenmiş (mimari-kararlar.md AD-23 bloğu, yerinde okundu) |
| AD-23 — üyelik seviyesi (K-F): "seviye yetki vermez, kapsam belirler" | ⚠ | AD-23'e paragraf eklendi ✓; ama seviye ile izin ekinin **bileşimi** ne AD'de ne planda tanımlı → R2-1. Ayrıca AD-23'ün "Bileşenler" bloğu hâlâ eski modeli söylüyor → Upstream Finding U-1 |
| AD-23 — kapsam seviyeleri + tek çözümleyici | ⚠ | Görev 2; A-11'in "null" ifadesi iki farklı null'u karıştırıyor → R2-4 |
| AD-23 güvenlik kuralı 1 + provisioning muafiyeti | ✓/⚠ | Görev 6 Adım 3 doğru öncülle; backoffice yolunun alt-küme muafiyeti açık yazılmamış → R2-5 |
| AD-23 güvenlik kuralı 2 (dört göz) | ✓ | Kapanış adresi tablosu 7 (onay turu) + "görev bir hatırlatmadır, onay bir karardır" sınırı |
| AD-23 — iki yol tek kural; kapı izne bağlı | ✓ | Görev 4 Adım 2 tablosu; A-5, A-23 |
| AD-23 — atamalar, kullanıcı çıkışı, onay omurgası | ✓ | Kapanış adresi tablosu 5, 6, 7, 8 |
| AD-09 — tehdit varsayımı yalnız süper admin; dar roller | ✓ | Muafiyet kapısı `office_user.*` izinlerinde (F-11 kapanışı) |
| AD-24 Ek 2 — sahip başına tablo, çekirdek kolon tekrarı, kapsam↔tablo kilidi | ✓/⚠ | Görev 8; `office_user_documents` kilidi de kuruyor. Eksik: silinmezlik trigger'ları → R2-6; kapsam değeri literal'i yanlış → R2-7 |
| AD-05 / AD-21 — şifreleme + erişim logu | ✓ | Görev 7 Adım 5, A-21 |
| AD-11 — silinmezlik | ⚠ | Kullanıcı/grup pasifleştirme ✓ (A-22); kritik belge trigger'ları instructor tablolarında planlanmamış → R2-6 |
| AD-26 — gruplar, `travelTo`, owner yasağı | ✓ | Görev 3 Adım 6, Görev 7 Adım 7 |
| çatı §174 — roller/kategoriler | ✓ | K-F bileşimi + `category` sözlüğü (spec §1.1 kodlarıyla birebir) |
| egitmen-spec §1, §1.1 | ✓ | Görev 7 (restrictOnDelete, kategori kodları) |
| egitmen-spec §1.2 — kadro değişikliği bildirim görevi | ⚠ | Kapanış adresi var (tablo 4) ama tetikleyici (işe alım) **bu turda** canlıya çıkıyor → R2-8 |
| egitmen-spec §2 — bakanlık ID, belge süresi, iki kuruluş, son eğitim tarihi | ✓ | Görev 7 Adım 1-3; sınır ayardan (A-15), `last_taught_on` elle açılmaz + adresli |
| egitmen-spec §2.1 — hassas veri rejimi | ✓ | Görev 7 Adım 5, A-21 |
| egitmen-spec §3 — uygunluk; yetkinlik §11/2 ertelemesi | ✓ | Kapanış adresi tablosu 2; A-19 belge ayağını kuruyor |
| egitmen-spec §4.1/4.3 — birim, boş ücret; §4.2 tarife | ✓ | Görev 7 Adım 4; tarife adresli (tablo 3), boş ücret uyarı |
| Tasarım 61/63 | ✓ | Görev 4/7/8/9 (r1'de doğrulanmıştı; kompozisyon bağlayıcı değil) |
| K-A / K-B / K-C / K-D / K-E / K-F | ✓/⚠ | K-A ✓ (A-1/A-2 + TTL ayarı) · K-B ✓ (A-23) · K-C ✓ (T-4 `uploadable_by`, A-17) · K-D ✓ (A-3; seviye Gate'e yetenek vermez) · K-E ✓ (A-7, polimorfik sahip) · K-F ⚠ → R2-1 |
| T-1…T-4 türetmeleri | ✓ | T-4 yeni ve yerinde: `uploadable_by` gerçekten var (`2026_08_04_000200:122`), üç değerin ekran davranışı tanımlı |

---

## ✅ What works

- v1'in kök hatası gerçekten kapanmış: **13 satırlık doğrulama tablosunun tamamı dosyayla örtüşüyor** — bu re-review'da tek tek kontrol edildi.
- `ScopedGate` ("görebilir mi" ↔ "neyi görebilir" ayrımı) + liste sorgusuna zorunlu `ScopeResolver` filtresi, rename'in iki riskini (kırılma / sessiz gevşeme) birlikte görüyor.
- K-F, F-5'in her iki yarısına da cevap: rol sözlüğü açılmıyor **ve** üyelik yetki taşımıyor — K-D'nin "kimse yetkiyle girmez" cümlesi korunuyor (aşağıda Soru 2).
- F-9 çözümü (`CompleteTask` insan / `ResolveTasks` olay) temiz; "kimse başkasının görevine dokunmaz" ile "kardeşler kapanır" artık çelişmiyor.
- Kapanış adresi tablosu 9 kalemiyle dürüst yazılmış; 7. kalemdeki *"görev bir hatırlatmadır, onay bir karardır"* sınırı ileride kapsam kaymasını önleyecek türden bir cümle.

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

**R2-1 — Üyelik seviyesi ile izin ekinin bileşimi tanımsız; ikisi aynı soruya farklı cevap verebilir.** K-F/AD-23: *"`staff` yalnız kendisini, `group_manager` ve `director` düğüm + altlarını kapsar."* Görev 2 Adım 3'ün `ScopeResolver` sözleşmesi ise seviyeden hiç bahsetmiyor: *"`group` → üyelik düğümleri + altları"*. `user.view.group` taşıyan **staff** ne görür — izin eki "grubum" diyor, seviyesi "yalnız kendin" diyor. Çözümleyici seviyeyi yok sayarsa K-F'nin "kapsam belirler" hükmü ölü kalır (seviye salt etiket olur); uygularsa `.group` eki staff için `.own`'a eşitlenir ve bunu plan hiçbir yerde söylemiyor. A-13 testi hangi seviyeyle koşulacak? Bileşim kuralı (ör. *etkin kapsam = min(izin eki, seviye kapsamı)*) tek cümleyle pinlenmeli — `groupsFor` imzasının seviyeyi nasıl kattığı dahil.

**R2-2 — Çağıran envanteri iki dosyayı atlıyor; `customer.view_all` rename'i canlı bir tenant-kapsam yolunu kırar.** `app/Support/Tenancy/SessionScopeResolver.php:73` bugün `allows('customer.view_all')` soruyor ve cevabı "**tüm aktif müşteriler**" kapsamını açıyor (dosyanın kendi başlığı: `customer.view_all → every active customer`). Rename sonrası bu ad ne çıplak ad (`customer.view`) ne yeni ad (`customer.view.all`) olduğundan `ScopedGate` da yakalamaz — o izni taşıyan herkesin müşteri kapsamı **sessizce daralır**. Ayrıca izin adlarının tek kayıt defteri `app/Support/Authorization/PermissionMatrix.php`'dir (`user.*` 128-132, `customer.view_all` 195; `OfficePermissionMatrix` yalnız onu saran sorgu — `OfficePermissionMatrix.php:13` "shape and labels come from PermissionMatrix, which is the one register") ve Görev 2'nin dosya listesinde **yok** — güncellenmezse yetki ekranı veritabanında artık var olmayan adları listeler ve atama sessizce boşa düşer. Görev 2 dosya listesine ikisi de girmeli; `ScopedPermissionMigrationTest`'e `customer.view_all` taşıyan kullanıcının müşteri kapsamının değişmediği kanıtı eklenmeli.

**R2-3 — Kapsam yalnız listede uygulanıyor; nesne-seviyesi rotalar `.group`/`.own`'u fiilen yok sayar.** `ScopedGate` bilinçli olarak "herhangi bir kapsam sürümü varsa geç" diyor; bu, kapsamın **her nesne rotasında ayrıca** sorulmasını şart koşar. Plan bunu yalnız `OfficeUsers` listesi (Adım 4 ⛔, A-13) ve `AssignOfficePermissions` hedefi (Görev 6 Adım 3/2) için istiyor. `office.users.show` (`UserController:62`, bugün `authorize('user.view')`), `user.update` ve `user.deactivate` uçları için `ScopeResolver::covers()` adımı **hiçbir görevde yok** — `user.view.group` taşıyan biri `/yonetim/kullanicilar/{id}` ile kapsam dışı herkesi açabilir, `.group` eki listede süs olur. Bu rotalara covers() adımı ve testi eklenmeli.

**R2-4 — A-11 iki farklı "null"u tek cümlede karıştırıyor.** `PermissionScope::split` kapsamsız isimde `[name, null]` döner (Adım 2) — bu null "kapsam eki yok" demek. `groupsFor`'un dönüş sözleşmesinde ise `null = sınırsız` (Adım 3). A-11 *"kapsamsız isimde null döner"* diyor; `groupsFor('audit.view') → null` diye uygulanırsa kapsamsız izin **sınırsız kapsam** almış olur — Adım 2'nin ⚠'i tam bunu yasaklıyordu ("All sayılmaz"). v1 bu durumda "hata verir" diyordu; v2 onu sessizce düşürmüş. `groupsFor`'un kapsamsız isimdeki davranışı (hata / ayrı dönüş tipi) açıkça pinlenmeli, A-11 ona göre yazılmalı.

**R2-5 — Backoffice'in devir yolunda alt-küme muafiyeti yazılmamış.** Görev 6 Adım 3 ⚠: backoffice *"`office_user.assign_permissions` ile geçer ve kapsam kuralından muaftır"*. Ama kuralın **birinci** yarısı alt-kümedir ve `beyondTheGranter` Gate üzerinden sorar — `BackofficeUser` ofis ailesinde hiçbir izin taşımadığından her izin "beyond" çıkar ve atama reddedilir. AD-23 muafiyeti tam da "taşımadığı yetkiyi atar" demektir; planın muafiyet cümlesi alt-kümeyi de kapsamalı (aksi ilk implementasyonda A-9/A-7 testlerini bozar ve geliştiriciyi plana aykırı doğaçlamaya iter).

**R2-6 — `instructor_documents` silinmezlik trigger'larını kopyalamıyor.** AD-24 Ek 2 çekirdek **kolonların** tekrarını söylüyor; ama `customer_documents`'ın davranış katmanında kolon olmayan iki koruma var: `app.deny_critical_document_delete` + `app.deny_critical_version_delete` trigger'ları (`2026_07_26_001000:261-331` — "those behaviours are owner agnostic"). Görev 8 Adım 1 yalnız kolon kopyasını tarif ediyor; eğitmen belgeleri de kritik olabilir (imzalı sözleşme, resmî evrak — AD-11). Trigger'lar `instructor_documents`/`office_user_documents` için de kurulmalı, test edilmeli.

**R2-7 — Kapsam literal'i yanlış: `scope = 'instructor'` değil `'egitmen'`.** Görev 8 Adım 2 `scope = 'instructor'` satırlarından okumayı söylüyor; gerçek sözlük `DocumentType::SCOPE_INSTRUCTOR = 'egitmen'` (`app/Models/DocumentType.php:56`; migration yorumu `2026_08_04_000200` de Türkçe değerleri sayıyor). Sabitle (`SCOPE_INSTRUCTOR`) yazılmalı; literal bırakılırsa sorgu boş döner. v2'nin kendi disiplini ("her iddia dosyadan") gereği düzeltilmeli.

**R2-8 — Kapanış tablosu 4. kalemin ertelemesi, tetikleyicisi bu turda canlıya çıkan kilitli-spec davranışını boşlukta bırakıyor.** `egitmen-spec` §1.2 (kilitli): *"eğitmen işe alım ve ayrılışında sistem bildirim görevi açar."* İşe alım (eğitmen kaydı) **bu turda** üretime giriyor ve `tasks` altyapısı + `OpenTask` bu turda var — görev türü eklemek bir `kind` değerinden ibaret. Erteleme kabul edilirse, bu turda açılan her eğitmen için 15 günlük KUMS bildirim hatırlatması hiç düşmeyecek (idari para cezası riski çatı §219). Ya `instructor.hired_notification` bu turda açılmalı, ya ertelemenin işletim riski Sets'e açıkça onaylatılmalı — mevcut hâliyle tablo satırı bu riski söylemiyor.

## ❓ Open Questions

1. **R2-1'in karar tarafı:** seviye×ek bileşimini kim tanımlar — plan mı, AD-23'e bir cümle mi? (Önerilen tek cümle: *etkin kapsam = min(izin eki, üyelik seviyesinin kapsamı)* — ama karar Sets'in.)
2. **`.group` eki staff'a verilebilir mi?** Bileşim min ise staff için `.group` = `.own`; yetki ekranı bunu gösterecek mi, engelleyecek mi?

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

1. **Görev 2 taşıması:** Kurgu (ScopedGate + liste filtresi + `.all`'a taşıma + `down()`) doğru; ama *"kimse bugünkünden az görmez"* güvencesi **henüz tam değil**: iki çağıran atlanmış — `SessionScopeResolver:73` (müşteri kapsamı sessizce daralır) ve izin adlarının tek kayıt defteri `Support/Authorization/PermissionMatrix` (R2-2). Ayrıca kapsam yalnız listede uygulanıyor; show/update/deactivate uçları covers() istemiyor (R2-3).
2. **K-F × K-D:** Uyumlu. Seviye Spatie/Gate'e hiçbir yetenek yazmıyor, üyelikli yeni kullanıcının etkin izin kümesi boş (A-3) — "kimse yetkiyle sisteme girmez" korunuyor. Kalan sorun yetki değil **kapsam** tarafında: seviye ile izin ekinin bileşimi tanımsız (R2-1).
3. **Kapanış tablosunun dürüstlüğü:** 9 kalemden 8'i gerçekten başka turların işi ve adresleri doğru (5-8 AD-23 kalemleri, 1-3 spec'in kendi ertelemeleri, 9 bilinçli K-F sonucu). Şüpheli tek kalem **4**: kadro değişikliği bildirimi — tetikleyicisi bu turda canlıya çıkıyor, altyapısı bu turda hazır, maliyeti bir `kind` değeri (R2-8).
4. **`instructor_documents` / AD-24 Ek 2:** Üç okuma da doğru — çekirdek kolon tekrarı Ek 2'nin açıkça kabul ettiği bedel; `tenant_id` yokluğu doğru (eğitmen dosyası 3E'nin verisidir — `documents_per_owner` migration'ının kendi gerekçesi); RLS uygulanmaması tutarlı (RLS tenant izolasyonudur, ofis tabloları bağlamsızdır). Eksik tek parça davranış katmanı: kritik belge silinmezlik trigger'ları (R2-6).

## ⚠️ Upstream Findings

**U-1 — AD-23 kendi içinde çelişiyor: yeni "üyelik seviyesi" paragrafı eklendi ama "Bileşenler" bloğu güncellenmedi.** Blok hâlâ *"**Rol** = yetki seti (backoffice tanımlı)"* ve *"**Üyelik** = (kişi, grup, rol) üçlüsü … farklı rollerle bulunabilir"* diyor — iki paragraf yukarıdaki *"üyelik üç seviyeden birini taşır; on bir sabit rol adı tutulmaz"* hükmüyle aynı anda doğru olamaz. Gelecek okuyucu (ve heyet) hangisinin geçerli olduğunu metinden çıkaramaz; Bileşenler bloğu K-F'ye göre revize edilmeli (Sets onayıyla — AD değişikliği kurucunundur).

## Verdict: ❌ REJECTED

16 bulgunun tamamı gerçek işle kapanmış ve doğrulama tablosu birebir doğru — v2.0 bambaşka bir kalitede. Kalan 8 bulgu dar ama üçü güvenlik/veri kaybı sınıfında (R2-1 kapsam belirsizliği, R2-2 sessiz kapsam daralması, R2-3 nesne-seviyesi kapsam boşluğu); bunlar pinlenmeden Görev 2 ve Görev 6 belirsizliğe düşmeden yazılamaz. R2-4…R2-8 küçük ama her biri ya bir test kriterini ya kilitli spec davranışını etkiliyor. Bir sonraki sürümde bu sekizin kapanışı + U-1'in Sets kararı beklenir.
