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

> **Review type:** plan-review (re-review, 3. tur)
> **Previous review:** `docs/reviews/review-ofis-t1-plan-2026-07-28-r2.md` (REJECTED — R2-1, R2-2 + 1 açık soru + 2 nit)
> **Upstream document(s) read:** `docs/specs/ofis-onyuz-spec.md` v1.4 (§5.3, karar 6/10, §9) · `docs/specs/omurga-spec.md` (§5.2 v1.16, A-1, B-5) · `CLAUDE.md`
> **Other cross-referenced / kod doğrulamaları:** plan v1.2 değişen bölümleri (görev 5 adım 2b `budgets()`, görev 6, görev 9 adım 2, görev 10 adım 3, revizyon geçmişi) · `app/Support/Auth/AnonymousRequestLimiter.php` (tek `$max`, iki anahtar — soru 2 için birebir okundu) · `app/Actions/Auth/RequestMagicLink.php` (limiter kullanımı) · `docs/issues.md` ("Giriş hız sınırı yalnız ofiste" kaydı + EGT-51 çift bağ)
> **Stage locks:** spec v1.4 APPROVED — **R2-2 kapanışı spec'i değiştirmeden uyum sağladı, kilit korunuyor.** Önceki turlarda kapanan kalemler yeniden açılmadı.

---

## R2-1 ve R2-2 kapanış doğrulaması

| Bulgu | Durum | Kanıt |
|---|---|---|
| **R2-1** — IP anahtarı e-posta ile aynı eşiği paylaşıyor | **Kapandı** | `budgets()` her anahtara kendi eşiğini veriyor (:1690-1702); `login_throttle.ip_max_attempts` yeni alan, tohum 30 (:1723); dört metot yeni yapıya geçmiş (:1636-1672); "adres bir kişidir, IP bir binadır" gerekçesi ve "tek alanla ayarlanamazdı" tespiti docblock'ta (:1677-1686) — bulgunun her iki yarısı (topoloji + ayarlanamazlık) kapanmış |
| **R2-2** — şifre değişiminde modal muafiyeti | **Kapandı — doğru yoldan** | Sets'e soruldu, karar "spec'e uy": muafiyet kaldırıldı, akış modal → form, formda mevcut-şifre alanı yok (görev 10 adım 3 ⚠ bloğu); dört işlemin aynı kapıdan geçmesi gerekçesi tutarlılık; **spec'e istisna işlenmedi** — kilit ihlalsiz. `ChangePasswordRequest`'ten `current_password` kuralının düşmesi modal'la tutarlı |
| **Açık soru 1** — EGT-51 bağlantısı | **Kapandı** | `docs/issues.md` kaydı EGT-24 (yüzey) + EGT-51 (prod kapısı) çift bağlı, "hangisi önce gelirse" kuralıyla |
| **Nit 1** — faktörsüz yolda `clear()` | **Kapandı** | :1721 — iki çağrı yeri de yazılı, ikisi de "giriş tamamlandı" anı |
| **Nit 2** — 500/503 local'de | **Kapandı** | :2406-2410 — `environment(['local','testing'])` istisnası yalnız 500/503 kolunda; 403/404/419 her ortamda |

---

## Sorulan dört noktanın değerlendirmesi

**1. `ip_max_attempts = 30` doğru büyüklük.** Kişi eşiğinin (5) bir kademe üstü: 15-20 kişilik ofisin kötü bir sabahı gerçekçi olarak 15 dakikada 10'un altında hata üretir — 30'a değmez; tek makineden adres listesi yürüyen biri ise 30. denemede durur ve 15 dakika bekler (~120 deneme/saat/IP — spray'i anlamlı yavaşlatır; IP sınırı doğası gereği hız tümseğidir, botnet'e karşı asıl kapı e-posta anahtarıdır ve o 5'te duruyor). Tohum makul; ekip belirgin büyürse (50+) operasyon teyidi (spec §15/4) artık **ayrı alanı** ayarlayabiliyor — R2-1'in asıl kazanımı buydu.

**2. `AnonymousRequestLimiter` bugün bu planda dokunulmaz — doğru; ama "aynı kusur değil" gerekçesi eksik ve kusur oradaki en kritik uçta DAHA keskin.** Sınıf okundu: aynı "tek `$max`, iki anahtar" deseni (:30-47) ve `RequestMagicLink` onu kullanıyor. Düşük frekans varsayımı şifre sıfırlama için doğru, **magic link için yanlış**: magic link katılımcı ekranlarının kapısıdır ve katılımcılar **aynı anda, aynı eğitim yeri wifi'sinden** gelir — 25 kişilik bir sınıf, eğitim başlamadan önceki 10 dakikada aynı IP'den 25 istek üretir; IP anahtarı düşük bir eşikte (sıfırlama sınıfı değerlerle 3/saat) dördüncü katılımcıdan itibaren reddeder. Bu, R2-1'in birebir sınıf eşi — NAT yerine salon wifi'si. Bu planın kapsamı değil (müşteri/katılımcı yüzeyi), ama **risk bugün hiçbir yerde kayıtlı değil**: `docs/issues.md`'deki kayıt "müşteri girişinin hız sınırı yok"u tutuyor, "mevcut limiter'ın IP bacağı sınıf senaryosunda kilitler"i tutmuyor — adressiz bilinen risk bulgudur → **R3-1**.

**3. İzin ayrışması plana açıkça yazılmalı.** Görev 6 adım 7'nin tanımı ("modal tek kullanımlık bir oturum izni yazar, arkasındaki action onu tüketir") tek-kullanımlıklığı sabitliyor ama **kapsamı** sabitlemiyor: paylaşılan tek bir oturum bayrağı bu cümleyi harfiyen karşılar ve şifre-değişimi için alınan onayın 2FA-kapatma tarafından tüketilmesine izin verir. Backoffice emsali Filament'in `mountUsing` sarmalaması sayesinde **yapısal olarak** işlem-bazlıydı; Inertia tarafında o yapı yok, kapsam sözleşmeye yazılmazsa jenerik bayrak yazılabilir. Bir cümle yeter — → **R3-2**.

**4. Kriter kümesi ve bağımlılıklar tutarlı — bir test boşluğu hariç.** Kriter eşlemesi (1,2,3,4,6,7,8) değişmedi ve doğru; görev sırası tutarlı (ThrottlesSignIn görev 5'te doğuyor, 6 tüketiyor — arayüz bildirimi :1479'da; modal mekanizması görev 6'da doğuyor, 10 tüketiyor). Boşluk: görev 10 adım 3'e şifre değiştirme akışı eklendi ama **adım 1'in test listesi güncellenmedi** — → **R3-3**.

---

## Upstream Coverage Matrix (bu turda değişen satırlar + kilit satırlar)

| Upstream maddesi | Durum | Kanıt (plan v1.2) |
|---|---|---|
| Omurga A-1 — hız sınırı, admin-tanımlı eşik/kilit | ✓ | Görev 5 adım 2b: iki ayrı config alanı, iki ayrı eşik; meşru-kullanıcı toplu kilidi kapatıldı |
| Omurga §5.2 v1.16 / ofis spec §5.3 — şifre adımı ayrı modal, listede şifre değişimi | ✓ | Görev 10 adım 3: modal → form, mevcut-şifre alanı formda değil; Sets kararı kayıtlı; spec kilidi korunmuş |
| Ofis spec karar 6 / §5.5 — bant ayrımı | ✓ | Değişmedi (r2'de ✓) |
| Ofis spec §4.6 — hata sayfaları | ✓ | Host guard + local/testing istisnası yalnız 500/503'te — spec'in görünür-hata ilkesiyle ve geliştirme gerçeğiyle birlikte tutarlı |
| Omurga B-5 — her deneme audit | ✓ | Görev 6 adım 5(b); değişmedi |
| Ofis spec karar 9 — sapma yok | ✓ | Bu turun tüm değişiklikleri mekanizma/ölçüm düzeltmesi; yeni ekran/bileşen doğmadı |
| Plan disiplini — her görev kendi test döngüsünü taşır | ⚠ | Görev 10 adım 3'ün akışı test listesine yansımadı — R3-3 |
| CLAUDE.md — adressiz erteleme yasağı | ⚠ | Magic link limiter'ının sınıf salonu riski kayıtsız — R3-1 |

---

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

**R3-1 — `AnonymousRequestLimiter`'ın IP bacağının sınıf salonu riski hiçbir yerde kayıtlı değil.** Planın 2. soruya dayanak yaptığı "düşük frekanslı uçlar, NAT sorunu aynı ağırlıkta değil" gerekçesi magic link ucunda yanlış: katılımcılar eğitim yerine **birlikte** gelir ve magic link **aynı salon wifi'sinden, aynı dakikalarda** istenir — tek `$max`'lı desen (sınıf okundu, :30-47) düşük IP eşiğinde dördüncü katılımcıdan itibaren reddeder. Ürünün çekirdek akışının kapısında R2-1 ile aynı sınıf bir kusur. Bu planın kapsamı değil; istenen yalnız **kayıt**: `docs/issues.md`'deki hız sınırı kaydına sınıf salonu senaryosu + limiter'ın `budgets()` desenine geçirilmesi ihtiyacı, katılımcı akışını kuran turun adresiyle eklenir; plandaki gerekçe cümlesi de düzeltilir. Adressiz bilinen risk bulgudur — adresiyle yazıldığında kapanır.

**R3-2 — Şifre onay izninin işlem kapsamı sözleşmeye yazılmalı (görev 6 adım 7).** "Tek kullanımlık oturum izni" kapsamsız: jenerik bir bayrak, şifre-değişimi onayının 2FA-kapatma tarafından tüketilmesine izin verir ve mevcut metni harfiyen karşılar. Backoffice emsalinin işlem-bazlılığı Filament'in yapısından geliyordu; Inertia tarafında sözleşmeye yazılmak zorunda. Eklenecek olan tek cümledir: *izin, onaylanan işlemin adına yazılır (`password_confirmed:<action>`), yalnız o işlem tüketir ve kısa bir süre sonra kendiliğinden düşer.* Görev 10 adım 3'ün "aynı kapıdan geçer" cümlesi de bu kapsam kuralına atıf almalı.

**R3-3 — Görev 10'un test listesi şifre değiştirme akışını kapsamıyor.** Adım 3 `ChangeOwnPassword`'ü A-4 audit'i ve oturum davranışıyla tanımlıyor; adım 1'in test listesi hâlâ v1.0'ın beş oturum senaryosunda. Eklenmesi gerekenler: modal izni olmadan endpoint reddediliyor · değişiklik audit üretiyor (aynı transaction) ve kullanıcıya bildiriliyor · **diğer** oturumlar kapanıyor, mevcut oturum ayakta · yeni şifre `Password::defaults()`'a takılınca hata `FormField`'a düşüyor. Planın kendi ilkesi: kanıtı olmayan akış teslim edilmiş sayılmaz.

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

Yok — dört soru da yukarıda cevaplandı; R3-1'in adres seçimi (katılımcı akışını hangi tur kuruyorsa o) kayıt yazılırken netleşir.

## 📎 Typos & Nits (non-blocking)

1. `ThrottlesSignIn` docblock'unun giriş paragrafı hâlâ *"like AnonymousRequestLimiter"* diyor (:1626) — artık tam benzemiyor (bütçeler ayrıştı); R3-1'in kaydıyla birlikte cümle güncellenirse iki sınıfın farkı da belgelenmiş olur.

## Verdict: ❌ REJECTED

R2-1 ve R2-2 tam ve doğru yoldan kapandı — R2-2'nin "Sets'e sor, spec'e uy, istisna işleme" çözümü sürecin çalıştığının kanıtı; açık soru ve iki nit de kapalı. Tohum 30 doğru büyüklük, kriter kümesi ve bağımlılıklar tutarlı. Kalan üç bulgu son kilometredir ve üçü de dar: R3-1 bir kayıt satırı + bir cümle düzeltmesi (adressiz bilinen risk bırakılamaz — kural planın değil projenin), R3-2 tek cümlelik kapsam sözleşmesi (güvenlik mekanizmasında belirsizlik bırakılamaz), R3-3 dört maddelik test listesi güncellemesi (planın kendi test-first disiplini). Üçü işlendiğinde planın onaylanmaması için görünür bir neden kalmıyor; son karar Sets'indir.
