# Review — Ofis T1 Uygulama Planı v1.1 — 2026-07-28 (r2)

> **Review type:** plan-review (re-review)
> **Previous review:** `docs/reviews/review-ofis-t1-plan-2026-07-28.md` (REJECTED — P-1…P-7 + 2 açık soru + 3 nit)
> **Upstream document(s) read:** `docs/specs/ofis-onyuz-spec.md` v1.4 (§5.3, §8.1, kararlar 6/10/11, §9) · `docs/specs/omurga-spec.md` (§5.2 v1.16, §5.4 A-1, B-5, T-1) · `CLAUDE.md`
> **Other cross-referenced / kod doğrulamaları:** `grep -rn "login_throttle" config/ app/ tests/` → yalnız `config/security.php:66` + yorum satırı — **genişleme iddiası bağımsız doğrulandı** · `config/app.php:110` (`app.hosts.office` mevcut — görev 9'un host guard'ı gerçek anahtara dayanıyor) · `app/Support/Auth/AnonymousRequestLimiter.php` (mevcut, desen kaynağı) · `tests/Feature/Omurga/HostRoutingTest.php:34-44` · plan v1.1 tam metni
> **Stage locks:** spec v1.4 APPROVED. Önceki turda kapanan kalemler yeniden açılmadı.

---

## P-1…P-7 kapanış doğrulaması

| Bulgu | Durum | Kanıt |
|---|---|---|
| **P-1** — challenge ucu A-1/B-5 dışında | **Kapandı — ve genişlemesi doğru teşhis** | Genişleme bağımsız doğrulandı: `login_throttle`'ı hiçbir kod okumuyor (grep, yukarıda). Görev 5 adım 2b `ThrottlesSignIn` (adres+IP, kilit süresi, `clear()` yalnız tamamlanmış girişte — "erken temizleme kod bütçesi verir" gerekçesi doğru); görev 6 adım 5(a) challenge'ı aynı sayaca bağlıyor; (b) `auth.second_factor_passed/failed` audit'i aynı transaction'da, kurtarma kodu ayrı olay (`auth.recovery_code_used` — açık soru 1'in cevabı gerekçeli); (c) bekleyen durum 5 dk TTL (`expires_at`, `travel(6)` testi). İki yeni test tam bulgunun senaryolarını ölçüyor: sınır sonrası doğru şifre/doğru kod reddi, pencere kapanınca giriş ekranına dönüş. `AuthenticationRefused::throttled()` mesajının hesap varlığını sızdırmaması (T-1) ayrıca düşünülmüş. Yeni kalibrasyon sorunu için bkz. **R2-1** |
| **P-2** — hata sayfası guard'ı ilk yüklemeyi dışlıyor | **Kapandı** | Görev 9 adım 2: koşul host oldu (`config('app.hosts.office')` — anahtar depoda mevcut, :110); gerekçe paragrafı bulguyu arşivliyor. Adım 1'in testi **başlıksız** isteği açıkça ölçüyor ("yalnız birincisi ölçülürse bulgu geri döner") — kapanışın kendisi regresyon korumalı |
| **P-3** — `office.home` testinin sahipsizliği | **Kapandı** | Görev 1 adım 10b: iki assert'ün yeni hâli yazılı, misafir beklentisi `assertRedirect()`, dosya "Değiştir" listesinde. `:34 assertNoContent` tespiti de doğru |
| **P-4** — Playwright'ın iki yanlış ölçümü | **Kapandı** | Görev 11 adım 3: seçici dokunma hedefleriyle sınırlı (`button, [role=button], input:not([type=hidden]), select, textarea`), inline caption bağlantısı istisnası gerekçeli; odak ölçümü `page.keyboard.press('Tab')` + `outline.style !== 'none'` iddiası eklendi |
| **P-5** — expectsJson ölçümü gerçek isteği kurmuyor | **Kapandı** | Görev 5 adım 1 ⚠ bloğu: `X-Requested-With: XMLHttpRequest` + `Accept: text/html, application/xhtml+xml` teste girdi; iki fıkra farkı (ajax/acceptsAnyContentType) açıklamasıyla — üretim kombinasyonu artık ölçülüyor |
| **P-6** — `config/security.php` görevsiz | **Kapandı** | Görev 0 adım 7: dört yöntem tohumu + "AuthSettings kapalı yöntemi zaten reddeder" notu; commit adımı (:183) dosyayı da ekliyor |
| **P-7** — üç öğenin arkası boş | **Kapandı — (b) yeni bulgu doğurdu** | (a) Görev 7 adım 5: `PasswordStrength` + `lib/password-strength.js`, bağımlılıksız ~20 satır kural, "gösterge politika değildir, sunucu kazanır" sınırı doğru. (b) Görev 10 adım 3: `ChangeOwnPassword`, A-4 audit + bildirim, diğer oturumlar kapanır — ama modal muafiyeti spec'le çelişiyor, bkz. **R2-2**. (c) Görev 9 adım 2b: `Toast` + "kritik bilgi Toast'ta taşınmaz" sınırı HANDOFF'la uyumlu |
| **Nit 1-3** | **Kapandı** | Adım numaraları ayrıştı; `SignInOfficeUser` görev 5'e taşındı (hız sınırı onu gerçekten değiştiriyor — harita artık doğru); `remember` düşürüldü ve gerekçesi koda yorum olarak yazıldı |
| **Açık soru 1** | **Kapandı** | Ayrı olay sınıfları, gerekçeli (görev 6 adım 5 ⚠) |
| **Açık soru 2** | **Kapandı** | "Açık bıraktıkları" 2: yerelde zorunlu / CI'da opsiyonel konumu ve kapanış raporu şartı duruyor |

**Sonuç: 12 kalemin 12'si kapandı.** İkisi kapanırken yeni birer bulgu doğurdu (R2-1, R2-2).

---

## P-1 genişlemesinin sınırı — sorulan değerlendirme

**Ofis+challenge şimdi, müşteri `EGT-24`'e adresli — sınır doğru.** Üç gerekçeyle:

1. Omurga planının kendi devir deseniyle tutarlı: *"yüzeyi kuran belge kuralı kendi metnine alır"* (devir kuralları tablosunun ilkesi). Ofis giriş yüzeyini kuran tur burası; müşteri giriş **ekranı** EGT-24'ün.
2. `SignInCustomerUser`'a bu turda dokunmak, kendi test yüzeyi (bootstrap kapısı, çift-tenant belirsizliği, chunk5 kriter 15) olan bir akışı T1 ticket'ının içine sokardı — kapsam disiplinine aykırı.
3. Erteleme adresli: plan "açık bıraktıkları" 3 + `docs/issues.md` kaydı + `EGT-24`. Adresli erteleme bulgu değildir.

**Tek şerh (açık soru 1):** müşteri giriş ucu ekranı olmasa da **canlı bir endpoint'tir** — boşluk ekran turuna değil, uca aittir. `EGT-24` prod çıkışından sonraya sarkarsa boşluk açıkta kalır; `docs/issues.md` kaydına *"prod çıkışından önce kapanmalı"* şerhiyle **EGT-51** (production hazırlık) bağlantısı eklenmeli. Backoffice tarafının Filament'in kendi hız sınırıyla ayrıca incelenmesi notu da yerinde.

---

## Upstream Coverage Matrix (spec v1.4 → plan v1.1, bu turda değişen satırlar + kilit satırlar)

| Spec maddesi | Durum | Kanıt (plan) |
|---|---|---|
| Omurga A-1 — hız sınırı, eşik/kilit admin-tanımlı | ✓ | Görev 5 adım 2b (config'ten okur), görev 6 adım 5(a); test doğru şifreyi de reddediyor. Kalibrasyon istisnası: **R2-1** |
| Omurga B-5 — her deneme audit | ✓ | Görev 6 adım 5(b) + üçüncü test iskeleti; kurtarma kodu ayrı olay |
| Ofis spec §8.1 — "kısıtla = sistemin ürettiği durum" | ✓ | Mekanizma artık gerçekten üretiliyor (planın kendi tespiti: mekanizmasız rozet hiç çıkmazdı); T2'nin "kısıtı kaldır"ının kaldıracağı şey doğdu |
| Ofis spec §4.6 — hata sayfaları hepsi Inertia | ✓ | Görev 9 adım 2 host guard + başlıksız istek testi |
| Ofis spec §4.5 — `expectsJson` ölçülerek doğrulanır | ✓ | Görev 5 adım 1-2, gerçek başlık seti |
| Ofis spec karar 11 — dört yöntem tohumu | ✓ | Görev 0 adım 7 |
| Ofis spec §6 T1 — şifre sıfırlama güç göstergesi | ✓ | Görev 7 adım 5; sunucu-kazanır sınırı |
| Ofis spec §6 T1 — profil-güvenlik (şifre kartı) | ⚠ | Görev 10 adım 3 akışı tamam; **modal muafiyeti spec §5.3 metniyle çelişiyor — R2-2** |
| Ofis spec §4.2 — flash prop'unun çizicisi | ✓ | Görev 9 adım 2b Toast; kritik/geçici ayrımı HANDOFF kuralıyla hizalı |
| Ofis spec karar 9 — sapma yok | ✓ | Bu turda eklenenlerin hepsi tasarım kaynaklı (PasswordStrength `10 Ortak Auth`'ta çizili, Toast `04`'ün deseni) |
| Ofis spec karar 10 — Playwright dar kapsam | ✓ | Görev 11 düzeltilmiş ölçümler; iş mantığı Pest'te |
| CLAUDE.md kırmızı çizgi 13 — audit aynı transaction | ✓ | Görev 6 adım 5(b) açıkça söylüyor |
| Spec T-1 — hesap varlığı sızdırılmaz | ✓ | `throttled()` mesaj notu; şifre sıfırlamada "aynı yanıt" testi duruyor |

---

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

**R2-1 — IP anahtarı e-posta anahtarıyla aynı eşiği paylaşıyor; ofis NAT'ı arkasında toplu kilit üretir (görev 5 adım 2b).** `ThrottlesSignIn::keys()` iki anahtar üretiyor (`email`, `ip`) ve `locked()` ikisini de **aynı** `max_attempts` (tohum: 5) ile soruyor. Ofis kullanıcılarının tamamı tek ofis IP'sinin arkasındadır: 15 dakikalık pencerede ekip genelinde **toplam 5** yanlış şifre — pazartesi sabahı sıradan bir olay — `sign-in:ip` anahtarını kilitler ve o IP'deki **herkes** 15 dakika giremez. Tek `max_attempts` değeriyle ikisi birlikte ayarlandığı için operasyon teyidi (spec §15/4) de bunu çözemez: e-posta eşiğini sıkı tutmak IP eşiğini de sıkılaştırır. IP anahtarına ayrı ve belirgin ölçüde yüksek bir eşik (config'te ayrı alan, ör. `ip_max_attempts`) ya da IP sayacının yalnız **farklı e-postalara dağılan** denemelerde devreye girmesi gerekir. (`AnonymousRequestLimiter` emsali düşük frekanslı uçlar içindi; giriş yüksek frekanslıdır — desen kopyalanırken bu fark kayboldu.)

**R2-2 — Görev 10 adım 3'ün "ayrı modal gerekmez" muafiyeti, onaylı spec metniyle çelişiyor.** Spec §5.3 (omurga §5.2 v1.16'dan): *"Profil değişikliklerinde şifre yeniden sorulur ve **ayrı bir modal**'dır, form alanı değil: e-posta değişimi, **şifre değişimi**, passkey ekleme/silme, ikinci faktör kurma/kapatma."* — şifre değişimi listede **adıyla** var ve kural form alanı yerleşimini açıkça reddediyor. Plan, `current_password`'ü formun kendi alanı yapıp *"sorulan şey adımın kendisidir, önündeki kapı değil"* gerekçesiyle muafiyet tanıyor. Gerekçe UX olarak savunulabilir (şifre değiştirme formunda mevcut-şifre alanı evrensel bir desendir ve v1.16'nın hedeflediği "form dolduran fark etmez" senaryosu burada zayıftır) — ama bu bir **spec revizyonudur ve plan seviyesinde sessizce yapılamaz** ("yorumlama yasak, sor" kuralının tam sınıfı). İki kapanış yolu var: (a) plan spec'e uyar (şifre değişimi de modal arkasına girer), ya da (b) Sets muafiyeti onaylar ve spec §5.3'e tek cümlelik istisna işlenir (üst belge revizyon listesine girerek). Karar Sets'indir; plan karar verilmeden uygulanamaz.

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

1. `docs/issues.md`'deki müşteri hız sınırı kaydına **EGT-51 bağlantısı** eklenecek mi? Uç bugün canlı; EGT-24 prod çıkışından sonraya sarkarsa boşluk prod'da açık kalır — "prod çıkışından önce kapanmalı" şerhi kaydı güvenceye alır.

## 📎 Typos & Nits (non-blocking)

1. **Faktörsüz yolda `clear()` çağrı yeri yazılmamış.** Kural "yalnız tamamlanmış girişte temizlenir" — 2FA'lı yolda challenge başarısında temizleniyor (görev 6); 2FA'sız kullanıcıda giriş `store()`'da tamamlanır ama oradaki ⚠ not "clear() burada çağrılmaz" diye okunabilir. Bir cümle: *"faktörü olmayan kullanıcıda `clear()` başarılı `store()` sonunda çağrılır."*
2. **Görev 9'un 500/503 eşlemesi local'de debug sayfasını gizler.** Inertia'nın belgelenmiş deseni `local`/`testing` ortamını 500'lerde dışlar; aksi hâlde geliştirme sırasında stack trace yerine jenerik `ServerError` görünür. `app()->environment()` koşulu 500/503 koluna eklenmeli (403/404/419 her ortamda eşlenebilir).

## Verdict: ❌ REJECTED

Önceki turun on iki kalemi de kanıtla kapandı; P-1'in genişlemesi bağımsız doğrulandı ve ofis/müşteri sınırı doğru çizilmiş (adresli erteleme + tek şerh: EGT-51 bağlantısı). Ancak kapanışların içinden iki yeni bulgu doğdu: **R2-1** hız sınırının IP bacağı, ofisin kendi ağ topolojisinde (tek NAT) meşru kullanıcıları toplu kilitleyecek şekilde kalibre edilmiş — yapı config'le düzeltilemiyor, anahtar tasarımı değişmeli; **R2-2** şifre değiştirme akışındaki modal muafiyeti, onaylı spec §5.3'ün adıyla saydığı bir kuralı plan seviyesinde deliyor — gerekçe savunulabilir ama karar Sets'in, planın değil. İkisi de dar: R2-1 tek sınıf + config alanı, R2-2 tek karar + tek cümle. Kapanınca re-review kısa sürer.
