# Review — Ofis T1 Uygulama Planı v1.0 — 2026-07-28

> **Review type:** plan-review (ilk review)
> **Previous review:** N/A
> **Upstream document(s) read:** `docs/specs/ofis-onyuz-spec.md` v1.4 (heyet 149 APPROVED) — kararlar 1–11, §4, §5, §6 T1, §7, §8, §9 · `docs/arch/kod-yerlesimi.md` (§1, §3) · `docs/arch/mimari-kararlar.md` (AD-25, AD-26) · `design/HANDOFF.md` · `CLAUDE.md`
> **Other cross-referenced / kod doğrulamaları:** `app/Actions/Auth/SignInOfficeUser.php` (User döndürüyor :35, `session()->regenerate()` :67 — plan iddiaları birebir doğru) · `config/auth.php` (`office_users` broker :140 ✓) · `vendor/pragmarx/{google2fa,google2fa-qrcode}` (ikisi de kurulu — "yeni paket girmiyor" iddiası ✓) · `app/Models/User.php` (`encrypted:array` cast :70 ✓) · `tests/Pest.php` (`officeUrl` :210 ✓) · `tests/Feature/Omurga/HostRoutingTest.php` (:36, :44 — bkz. P-3) · `bootstrap/app.php` · `routes/office.php` · `config/security.php`
> **Stage locks:** spec v1.4 APPROVED; plan spec'i değiştirmiyor, tek türetmesini açıkça işaretliyor (aşağıda ✓).

---

## Upstream Coverage Matrix (spec v1.4 → plan)

| Spec maddesi | Durum | Kanıt (plan) |
|---|---|---|
| Karar 1 — atomlar baştan, molekül/organizma ekranla | ✓ | Görev 4 yalnız atomlar; FormField görev 5, StatusBadge/LockNote/PageHeader görev 8, Banner görev 9, SessionRow görev 10 — hepsi ilk ihtiyaç ekranıyla. Badge/StatusBadge ayrımı (ton atomda, eşleme molekülde) spec ruhuna uygun |
| Karar 2 — ilk yolculuk giriş → kabuk → kullanıcılar | ✓ | Görev 5 → 8; kullanıcılar ekranının kendisi T2 (doğru sınır), kriter 3'ün testi mevcut backend'le görev 8'de |
| Karar 3 — çeviri çalışma zamanında, ince `t()` | ✓ | Görev 3: `TranslationBundle` + `t()`/`choose`; eksik grup ham anahtar olarak görünür (i18n.js); sayfa bildirimi `withTranslations` |
| Karar 4 — kabuk route'tan | ✓ | Görev 1 adım 8: `Inertia::setRootView('office')` route dosyasında; `HandleInertiaRequests` yorumu kararı taşıyor |
| Karar 5 — derleme girişi panel başına | ✓ | Görev 1 adım 2/4 (glob yalnız `pages/Office/**`), görev 2 arch testi (kriter 6), görev 0 adım 2 kural revizyonu |
| Karar 6 — ikinci faktör yetki bazlı, kurulum dayatılmaz | ✓ | Görev 6 adım 4 yorumu + görev 9 adım 3 bandı (`has_mfa_gated_permission`); türetmenin karar 6 analizi doğru — aşağıda soru 1 |
| Karar 8 — Filament ayrı, tek token kaynağı dışlama ifadesiyle | ✓ | Görev 0 adım 1 ve 3 spec §7'nin son metnini (dışlama) birebir uyguluyor — r3 nit'i de kapanmış |
| Karar 9 — tasarımdan sapma yok | ✓ | Üretilen her ekran/desen `design/` kaynaklı; logo kopyalama HANDOFF "açık kalanlar"ın kendi talebi. Dördüncü sapma bulunamadı |
| Karar 10 — Pest + Inertia zorunlu; Playwright dar | ✓ | Her görev kendi Pest döngüsüyle; görev 11 yalnız kriter 7-8 (ölçüm hataları için bkz. P-4) |
| Karar 11 — dört yöntem; config tohumları | ⚠ | Dosya haritasında var, **hiçbir görev adımı yapmıyor** — P-6 |
| §4.1 kurulum (paketler, root view, fontlar repoda) | ✓ | Görev 1 adım 1-3, görev 2 adım 1-3; bunny eklentisinin kaldırılması fontlar repoya alındığı için tutarlı |
| §4.2 paylaşılan prop'lar (auth·navigation·flash·translations·locale) | ⚠ | Görev 1 adım 6 beşini de kuruyor; ama `flash`'in çizicisi (Toast) hiçbir görevde doğmuyor — P-7c |
| §4.3 token katmanı birebir | ✓ | Görev 2 adım 2: ink 8 basamak, yüzeyler, accent, 6 semantik dörtlü, viz 8, spacing 4'lük, radius 4'lü, gölge 2 seviye, breakpoint 4'lü, tipografi 8 kademe + `tnum` — `00 Tokenlar` kalemleriyle eksiksiz eşleşiyor |
| §4.4 atomic yapı + kurallar | ✓ | Dosya haritası spec ağacıyla uyumlu; sayfalar kompozisyon, hesap `lib/`'te |
| §4.5 veri akışı (Controller→Query→Data; Action `__invoke`; `expectsJson` ölçümü) | ⚠ | Katman disiplini görevlerde doğru; `expectsJson` ölçümünün kendisi gerçek başlık setini kurmuyor — P-5 |
| §4.6 hata yüzeyi — hepsi Inertia sayfası | ❌ | Görev 9 adım 2'nin `X-Inertia` guard'ı ilk yüklemeyi dışlıyor; doğrudan gezinen tarayıcı stok Laravel sayfası alır — P-2 |
| §5.1 yöntem tablosu (şifre T1, 2FA T1, magic link/passkey T3) | ✓ | Görev 5-6 T1 yarısı; T3 kalemleri planda yok (doğru kapsam) |
| §5.3 şifre yeniden sorma ayrı modal; kapı sunucuda | ✓ | Görev 6 adım 7: `ConfirmModal` + tek kullanımlık oturum izni, action tüketir |
| §5.4 / §5.5 (T3-T2 kalemleri) | ✓ | Planda yok — spec §6 adresleriyle; görev 9 adım 3 bandın §5.5 ayrımını açıkça koruyor |
| §6 T1 ekran envanteri (6 satır) | ⚠ | Giriş ✓ (görev 5), 2FA doğrulama ✓ (görev 6), şifre sıfırlama ⚠ (güç göstergesi sahipsiz — P-7a), profil-güvenlik ⚠ (şifre kartının arkasında değişiklik action'ı yok — P-7b), hata sayfaları ✓/❌ (P-2), panel kabuğu ✓ (görev 8) |
| §7 üst belge revizyonları | ✓ | Görev 0 yedi adımda listenin doküman kalemlerini birebir işliyor (metinler spec'le uyumlu); tek kod kalemi sahipsiz — P-6 |
| §8.1/§8.4 (T2 kalemleri) | ✓ | Planda yok — doğru kapsam |
| §8.3 onaylı sapmalar | ✓ | T1'e düşen tek sapma (uyarı bandı) görev 9'da; diğer ikisi T3 |
| §9 kriter eşlemesi (1,2,3,4,6,7,8 → T1) | ✓ | Öz-denetim tablosu doğru: 1→5 · 2→2 · 3→8 · 4→3 · 6→2 · 7,8→11; 5/12/13→T2, 9/10/11→T3 spec §6 ile tutarlı |
| Omurga A-1 (hız sınırı) · B-5 (her deneme audit) — giriş akışına uygulanışı | ❌ | İkinci faktör challenge ucu için ikisi de planda yok — P-1 |
| Test disiplini (AD-26: grup, çıplak `now()` yasağı, lokal kadans) | ✓ | Global kısıtlar bölümü; görevler `omurga` grubunda; TeamCity yalnız kapanışta (Sets'in 28.07 kadans kararıyla uyumlu) |
| CLAUDE.md kırmızı çizgiler (audit aynı transaction, `env()` yalnız config, migration yok) | ✓ | Global kısıtlar + görev 6 adım 6; T1 şema değiştirmiyor |

---

## Özellikle sorulan sekiz noktanın değerlendirmesi

1. **Türetme (kurulu faktör her girişte sorulur) — doğru ve gerekli; karar 6'yı bozmuyor.** Ayrım (kurulum ≠ kullanım) upstream'de zaten var: omurga §5.2 v1.15 kuralı **oturuma** sorar ve tam bu hata sınıfını arşivler — *"passkey kaydetmek şifreyle girişi sessizce tek faktöre düşürüyordu."* Türetme yapılmazsa aynısı ofiste tekrar eder: `hasCompletedMfa()` hesap düzeyindedir; faktör girişte sorulmazsa şifre-tek oturum MFA'lı izinleri **etkin** kullanır — karar 6'nın kilidi anlamsızlaşır. Zorlanan şey kurulum değil (o teklif kalıyor), kurulmuş olanın kullanılması. "Sets'in itirazına açık" işareti de doğru usul.
2. **Görev 6 giriş deseni — çekirdeği doğru, kanıtı depoda.** `SignInOfficeUser` zaten `User` döndürüyor ve `session()->regenerate()`'i şifre doğrulandıktan sonra çağırıyor (:67) — fixation penceresi kapalı; `logout()` yalnız guard verisini temizler, `pending_user` ve `url.intended` hayatta kalır — `intended()` korunur. Yarım-kimlik oturumu **yok**: guard boş, kimlik yalnız pending anahtarında ve test bunu açıkça ölçüyor ("Not signed in yet"). Eksik olan uç sertleştirmesi — P-1.
3. **`expectsJson` dallanması — analiz doğru, ölçüm eksik.** Gerçek Inertia isteği `Accept: text/html, application/xhtml+xml` taşıdığı için `expectsJson()` zaten false döner ve redirect dalına düşer; önerilen `! $request->header('X-Inertia')` yedeği de müşteri tarafını bozmaz (bugün X-Inertia gönderen tüketici yok). Ama planın testi gerçek başlık setini kurmuyor — P-5.
4. **Görev 9 sırası doğru, guard'ı yanlış.** `render(HttpException)` callback'i istisna render'ı sırasında koşup null döner (audit yazılır), `$exceptions->respond()` bitmiş yanıt üzerinde çalışır — çakışmaz, anlayış doğru. Yanlış olan `X-Inertia === null → dokunma` koşulu — P-2.
5. **Katman adresleri doğru.** `AppAuthenticator` üretir/çizer/doğrular, karar vermez (kararlar action ve policy'de) — `app/Support/Auth/` doğru adres; `TranslationBundle` istek metaverisini çeviriye çevirir — `app/Support/Inertia/` doğru; `withTranslations()` sunum bildirimi yazar, iş yapmaz. Paket iddiası doğrulandı: `pragmarx/google2fa` **ve** `google2fa-qrcode` vendor'da — yeni paket girmiyor.
6. **Kriter eşlemesi doğru** (matrix satırı); kapsanmayan iki kalem P-6 (config tohumları) ve P-7 (güç göstergesi, şifre kartı, Toast).
7. **Atom listesi:** T1'de kullanılmayacak atomlar (Select, SearchableSelect, Checkbox, Radio, Toggle, Chip…) karar 1'in açık talebi — bulgu değil. Eksik olan tersi: T1 ekranının istediği ama hiçbir görevin üretmediği bileşenler — P-7.
8. **Tasarım sadakati:** dördüncü sapma bulunamadı. Üretilen her şey `10 Ortak Auth` / `05 Navigasyon` / `04 Geri Bildirim` / `06 Desen Ekleri` / `01-02` kaynaklı; logo kopyalama HANDOFF'un kendi açık kalanlar maddesini kapatıyor.

---

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

**P-1 — İkinci faktör challenge ucu A-1/B-5 kapsamı dışında bırakılmış (görev 6).** `POST /ikinci-faktor` için: (a) **hız sınırı yok** — 6 haneli TOTP, `window: 1` ile her 30 saniyede ~3 geçerli kod demektir; şifresi ele geçmiş bir hesapta sınırsız deneme, ikinci faktörü çevrimiçi brute-force'a açar. Omurga A-1 *"başarısız giriş denemeleri hız sınırına tabidir"* der ve challenge bir giriş denemesidir. (b) **Challenge sonucunun audit'i yazılmamış** — şifre adımı `attempt()` başarısında B-5 audit'i üretir; faktör reddi planda sessiz. Sonuç: audit'te "başarılı giriş" görünen ama hiç oturum açamamış (faktörde takılmış) denemeler iz bırakmaz — B-5 *"başarılı ve başarısız her deneme"* der. (c) `office_second_factor.pending_user` durumunun **süresi tanımsız** — şifresi kanıtlanmış yarı-durum, oturum ömrü boyunca (60 dk idle) bekliyor; makul bir TTL yazılmalı. Üçü de görev 6 adım 5'in tanımına girer.

**P-2 — Hata sayfası guard'ı ilk yüklemeyi dışlıyor (görev 9 adım 2).** `if ($request->header('X-Inertia') === null) return $response;` — adres çubuğuna yazılan URL, e-postadan gelen bağlantı, yenilenen sayfa X-Inertia taşımaz; bu isteklerde stok Laravel hata sayfası döner ve spec §4.6 (*"403/404/500/bakım/oturum — hepsi Inertia sayfası"*) tam ilk-yükleme senaryosunda ihlal edilir. `Inertia::render()->toResponse()` Inertia olmayan istekte zaten tam HTML sayfası üretir — Inertia'nın belgelenmiş hata sayfası deseni bu yüzden guard'sızdır. Doğru koşul header değil **host**tur: ofis host'undaki her istek eşlenir (diğer host'lar kendi turlarında kendi bileşenlerini bağlar).

**P-3 — Görev 1 adım 10 kaldırdığı rotanın mevcut testini üstlenmiyor.** `office.home` siliniyor; `tests/Feature/Omurga/HostRoutingTest.php:36` o ada `toBe('office.home')` diyor ve `:44` de referans veriyor. Ayrıca `/` artık `auth:office` arkasında — misafir beklentisi değişir. Test dosyası planın "Değişecek" listesinde yok; ilk `--group=omurga` koşusunda kırmızı sürpriz olarak bulunur. Plan bu güncellemeyi açıkça üstlenmeli (hangi assert nasıl değişiyor).

**P-4 — Görev 11'in iki ölçümü yanlış kırmızı üretir.** (a) 44px iddiası `a` dahil **tüm** etkileşimli öğelere uygulanıyor; giriş ekranının kendi tasarımındaki caption bağlantısı ("şifremi unuttum", `text-caption`) ~16-18px yüksekliğindedir ve test tasarıma uygun öğede kırılır. HANDOFF'un kuralı dokunma **hedefleri** içindir (form kontrolü/buton); satır içi bağlantı WCAG'ın da adlandırılmış istisnasıdır — seçici daraltılmalı. (b) `element.focus()` programatik odaktır; `:focus-visible` programatik odakta eşleşmeyebilir → `outline: none` okunur ve focus-ring iddiası sahte düşer. Ölçüm klavye gezinmesiyle (`page.keyboard.press('Tab')`) yapılmalı.

**P-5 — `expectsJson` ölçümü gerçek isteği kurmuyor (görev 5 adım 1, üçüncü test).** Test yalnız `X-Inertia` gönderiyor; gerçek Inertia isteği `X-Requested-With: XMLHttpRequest` + `Accept: text/html, application/xhtml+xml` taşır ve `expectsJson()`'dan **farklı bir fıkrayla** geçer (testte `acceptsAnyContentType` üzerinden, gerçekte `Accept` üzerinden). İki yol bugün aynı dala düşüyor ama test, üretim kombinasyonunu kanıtlamıyor — "ölçerek doğrulanır" diyen kalemin ölçümü eksik. Gerçek başlık seti teste eklenmeli.

**P-6 — `config/security.php` değişikliği görevsiz.** Dosya haritası `default_methods.office`'a `magic_link` + `passkey` eklenmesini listeliyor (karar 11, spec §7 satırı) ama hiçbir görev adımı bunu yapmıyor — görev 0 yalnız doküman kalemlerini işliyor. Karar 11'in tek kod revizyonu sahipsiz; ya görev 0'a adım eklenir ya açıkça T3'e adreslenir (bugün eklemek zararsız ama sahipsiz bırakmak plan disiplinine aykırı — adressiz kalem bulgudur).

**P-7 — Üç ekran öğesinin arkası boş.** (a) **Şifre güç göstergesi** (spec §6 T1 "şifre sıfırlama … güç göstergesi"; görev 7 adım 5 ekranda anıyor): bileşeni hiçbir görevin dosya listesinde yok ve gücün **neyle** hesaplanacağı (kural seti mi, kitaplık mı — yeni bağımlılık kararı olabilir) yazılmamış. (b) **Görev 10'un "şifre kartı"**: profilde şifre değiştirmenin action'ı/endpoint'i planda yok — A-4 gereği audit'li hassas işlemdir; ya iş kalemi eklenir ya kart T1'de salt-bilgi olarak tanımlanıp değişiklik adresli ertelenir. (c) **`flash` prop'unun çizicisi yok**: prop görev 1'de paylaşılıyor, görev 10'un aksiyonları doğal olarak flash üretir, ama Toast (spec §4.4 molekül listesinde) hiçbir görevde doğmuyor — üretilen flash ekranda görünmez.

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

1. Görev 6'da kurtarma kodu **tek kullanımlık düşürme** audit'leniyor (adım 5) — challenge audit'i P-1 ile eklendiğinde ikisi aynı olay sınıfında mı birleşir, ayrı mı kalır? (Uygulayıcıya tek cümlelik yönlendirme yeter.)
2. Görev 11 adım 5'in CI adresi "açık bıraktılar"da adresli (✓ bulgu değil); yerelde-zorunlu/CI'da-opsiyonel konumuna düşerse kriter 7-8'in kapanış kanıtının **nerede koştuğu** kapanış raporunda açıkça yazılmalı.

## 📎 Typos & Nits (non-blocking)

1. Görev 1'de iki ayrı **"Adım 14"** var (CI build adımı ve kalite kapısı) — numaralandırma kayar.
2. Görev 6 Dosyalar *"Değiştir: `SignInOfficeUser.php`"* diyor, adım 4 *"değişmez"* diyor — kod zaten `User` döndürdüğü için değişiklik gerekmiyor; harita satırı düşmeli.
3. Giriş formunda `remember` alanı var (`useForm({ …, remember: false })`) ama şablonda kontrolü yok — ya Checkbox çizilir ya alan düşer.

## Verdict: ❌ REJECTED

Planın omurgası sağlam: türetme doğru ve gerekli (soru 1), görev 6'nın oturum deseni kod kanıtıyla doğru (soru 2), katman adresleri ve paket iddiaları depoda birebir doğrulandı (soru 5), kriter eşlemesi tutarlı (soru 6), tasarım sadakati tam (soru 8). Ancak yedi bulgu var ve ikisi dar değil: **P-1** ikinci faktör ucunu hız sınırsız ve audit'siz bırakıyor (A-1/B-5 ihlali — güvenlik), **P-2** hata sayfalarını tam ilk-yükleme senaryosunda spec §4.6'ya aykırı düşürüyor. Kalan beşi (P-3…P-7) küçük ama planın kendi disiplinine göre adressiz iş bırakıyor ya da yanlış ölçüm yazıyor. Bulgular işlenip re-review istenmelidir; hiçbiri planın yapısını bozmuyor — yedisi de görev içi ekleme/düzeltmeyle kapanır.
