# Review — Omurga Chunk 2 (Tenancy, RLS, Tenant Context) Implementasyonu — 2026-07-26 · r3

> **Review type:** impl-review (re-review)
> **Previous review:** `docs/reviews/review-omurga-chunk2-impl-2026-07-26-r2.md` — REJECTED (R2-1, R2-2 + açık soru 1)
> **Upstream document(s) read:** r1/r2'deki set geçerli; bu turda ek olarak `docs/claude/heyet.md` (ofis ≠ backoffice düzeltmesi) ve `docs/issues.md` yeniden okundu
> **Other cross-referenced documents:** `vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php` (R2-1 mekanizmasının dayanağı, r2'de doğrulanmıştı)
> **Stage locks in effect:** r1 ile aynı; plan §2.8.1 düzeltme notu yürürlükte.

**Kanıt tabanı:** tek düzeltme commit'i `8034c07` (11 dosya, +271/−62; `ada42f1..HEAD` diff'i satır satır okundu). TeamCity **#701: SUCCESS — 122 passed, 0 failed, 0 ignored** (73 `omurga` + 49 `omurga-rls`; artış tutarlı: ConnectionSafety +2). Pint `--test`: passed; PHPStan level 6: 0 hata (ikisi de bu review sırasında lokal koşuldu).

---

## Önceki bulguların kapanış matrisi

| r2 bulgusu | Durum | Kanıt |
|---|---|---|
| **R2-1** — superuser kontrolü uzun ömürlü süreçte ilk hatadan sonra devreden çıkıyordu | ✓ | Kontrol bağlantı açılışından **sorgu yoluna** taşındı: `app/Support/Tenancy/ConnectionRoleGuard.php` — `beforeExecuting` içinden, tenant bekçisinden **önce** (`TenancyServiceProvider.php:64-76`, "first question asked of every query"). Verdict yalnız **SAFE** ise cache'leniyor (`:89`); UNSAFE her sorguda fırlatıyor (`:59-61` — sorgu yürütülmeden); sonuçsuz kontrol kaydedilmiyor (`:77-81`); kendi lookup'ı için reentrancy bayrağı (`:48, 63-75`). r2'nin iki mekanizması da etkisiz: manager'ın bağlantıyı event'ten önce cache'lemesi artık önemsiz (kontrol sorguda), "başarısızlığı cache'leme" tersine döndü (yalnız başarı cache'lenir). Kilit test: `ConnectionSafetyTest.php:49-70` **'keeps refusing after the first refusal'** — aynı süreçte cache'lenmiş superuser bağlantısına üç ardışık sorgu, üçü de fırlatıyor; kontrol establishment'a geri taşınırsa attempt 2-3 sessizce geçeceği için test kırılır — doğru regresyon kilidi |
| **R2-2** — `DB_APP_PASSWORD` referans kalıntıları | ✓ | Üç dosya süpürüldü: `userlist.txt.example:26-31` (metin + placeholder → `SET_TO_DB_PASSWORD`; "the tempting fix ... is the one this file exists to forbid" cümlesiyle), `deployment/pgbouncer/README.md:16-17`, `.env.testing.example:41-42`. Kalan dört geçiş tamamı "ayrı bir DB_APP_PASSWORD yok" **olumsuzlaması** — kasıtlı ve doğru (grep ile doğrulandı) |
| **Açık soru 1** — `DB_CONNECTION=pgsql_admin` vektörü | ✓ | `TenancyServiceProvider::refuseAdminConnectionAsDefault()` (`:89-110`) boot'ta reddediyor; gerekçe yorumu simetrik boşluğu adlandırıyor. Boot-anı kontrolü doğru seçim: `Migrator::usingConnection`'ın koşum sırasında default'u geçici `pgsql_admin` yapması boot'tan sonra olduğu için migration akışı kırılmıyor (122 test yeşil bunu kanıtlıyor). Test: `ConnectionSafetyTest.php:72-82` |
| **Açık soru 2** — `DB_USERNAME` ↔ `DB_APP_ROLE` | ✓ (karar bildirimi) | Kalıcı kontrol eklenmedi; gerekçe tutarlı — tehlikeli ayrışma sonucu (yanlış/superuser rolle bağlanma) `ConnectionRoleGuard`'a takılır, tehlikesiz sonuç migration'ın anlaşılır hatasıdır. Not olarak kabul |

## Kapsam dışı düzeltme — ofis ≠ backoffice gerekçe düzeltmesi

F-4'ün **kararı** değişmedi (middleware panelde, `HostRoutingTest.php:63-74` kilidi duruyor), **gerekçesi** düzeltildi: backoffice ofis kapsamı taşımaz; yönettiği tanım tabloları §2.8'de bağlamsızdır. Yeni gerekçe — "panele ileride tenant-kapsamlı kaynak eklenirse kapsamsız sorgu atmak yerine yüksek sesle düşsün" — davranışla birebir tutarlı: `SessionScopeResolver` backoffice kimliğine boş kapsam verir, boş kapsam `TenantContext::ambientScope()`'ta isimli exception'dır. Yorumlar (`BackofficePanelProvider.php:58-70`, `HostRoutingTest.php:63-69`) ve `docs/claude/heyet.md` (ofis/backoffice tablosu + "en geniş ofis yetkisi" gerekçesinin bulgu konusu ilan edilmesi) güncellendi. `docs/issues.md`'ye eklenen `backoffice_users` değerlendirmesi doğru disiplinle kayıtlı: karar chunk5'te, ayırma yönünde karar **spec değişikliği** sayılacak.

**Regresyon taraması:** diff'in dokunduğu davranış alanı yalnız rol kontrolü, boot kontrolü ve yorum/doküman düzeltmeleri; r1/r2 matrislerinin diğer satırları etkilenmedi, tam suite yeşil.

## ✅ What works

- **R2-1'in kapanışı doğru katmanda:** "hangi olayda kontrol edilir" sorusunun cevabı framework'ün cache semantiğinden bağımsızlaştırıldı — sorgu yolu, bağlantının nasıl ele geçtiğini umursamıyor. SAFE-only cache + sonuçsuz-kontrol-tekrarı + reentrancy bayrağı, r2'de sayılan üç zayıflığın üçünü de tek tasarımla kapatıyor.
- **'keeps refusing' testi** tam olarak r2'nin senaryosunu (cache'lenmiş bağlantıya ardışık sorgular) kilitliyor; regresyonun iki yönü de (establishment'a geri taşıma, başarısızlık cache'i) testi kırar.
- **Açık soru 1'in kapanışında boot-anı seçimi** migrator'ın geçici default değişimiyle çakışmayı bilinçli olarak önlüyor — mekanizmanın neden sürekli değil boot-anı olduğu koddan okunuyor.
- **R2-2 süpürmesi olumsuzlamalarla bitirilmiş** — dosyaların uyarı işlevi ("yanlış düzeltmeyi engellemek") korunarak.
- **Gerekçe düzeltmesini kendiliğinden beyan etmek** (karar aynı kalsa da) ve heyet prompt'una "yanlış gerekçe bulgu konusudur" satırını ekletmek — review disiplininin içselleştiği yer burası.

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

Bulgu yok.

**Bloklamayan iki gözlem (kayıt için):**

1. `ConnectionRoleGuard::verify`'da sonuçsuz kontrol (`$role === null`, `:77-81`) o **tek sorguyu** verdict'siz geçirir — cache'lenmediği doğru, ama ilgili sorgu yine de yürür. Pratikte erişilmez bir dal: `current_user` için `pg_roles` satırı PostgreSQL'de daima vardır (oturum açıkken rolün düşürülmesi gibi egzotik durumlar hariç). Davranış tercihi olarak kabul edilebilir; dal bir gün gerçekleşirse fail-closed'a çevrilmesi düşünülebilir.
2. SAFE verdict'i süreç ömrünce kalıcıdır: uzun ömürlü bir süreçte, aynı ada sahip rolün **veritabanı tarafında** sonradan superuser yapılması (`ALTER ROLE ... SUPERUSER`) cache'i geçersiz kılmaz. Kontrolün tehdit modeli ortam hatasıdır, DB tarafında aktif değişiklik değil — kapsam dışı sayılması tutarlı; not düşüldü.

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

Bloklayan soru yok. Sonraki aşamalara taşınan kayıtlı kalemler: chunk5 D-1…D-5 (taşınan kriterler), `backoffice_users` kararı (chunk5, gerekirse spec değişikliği), chunk9 kural taraması kalemleri (`set_config`, bekçi regex sınırları), chunk3 `audit.tables`↔`exempt_tables` netleşmesi — tümü `docs/issues.md`'de adresli.

## Verdict: ✅ APPROVED

R2-1 doğru katmanda, framework semantiğine yaslanmadan ve iki yönlü regresyon kilidiyle kapatıldı; R2-2 süpürmesi tam; açık soru 1 vektörü boot kontrolüyle kapandı. r1'in beş bulgusu, r2'nin iki bulgusu ve üç açık sorunun tamamı kapalı; kabul kriterleri matrisi (r1 + r2 + r3 delta'ları) chunk2'nin kalan kapsamının tamamında ✓. CI #701 (122/0/0), Pint ve PHPStan bağımsız doğrulandı. Chunk2 kapsamındaki iş, taşınan maddelerin chunk5 kaydıyla birlikte plana uygundur. Son karar Sets'indir.
