# Review — Plan: Platform Omurgası (EGT-21) — Sürüm 1.0 — 25.07.2026

> **Review type:** plan-review (index + 9 chunk, bütün olarak)
> **Reviewed:** `docs/plans/omurga-plan.md` + `omurga-plan_chunk1..9.md` (tamamı okundu)
> **Upstream (kilitli):** `docs/specs/omurga-spec.md` v1.4 — APPROVED (`review-omurga-spec-2026-07-25-r5.md`) · AD-01…AD-28 · `docs/arch/kod-yerlesimi.md` (AD-25)
> **Stage locks in effect:** spec v1.4 kararları tartışılmaz · ⛔ sahip kararı: otomatik veri silme/anonimleştirme yok — bu review o konuyu bulgu yapmaz (planın bu kararı doğru uyguladığı chunk4 §4.3/§4.7-12 ve chunk9 §9.5'te teyit edildi)

---

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

| Spec § | Plan karşılığı | Durum | Kanıt |
|---|---|---|---|
| §3, §3.1 (kullanıcı türleri, gerçek kişi tenant) | chunk5 §5.1 | ✓ | `tur` enum, `(tenant_id, tur, email)` unique — spec §13 istisnası birebir |
| §3.2 (I-1…I-4, kimlik tenant sınırını aşmaz) | chunk2 §2.6, chunk4 §4.1, chunk9 §9.5 | ✓ | Global unique yok; tenant-anahtarlı `tckn_hash` çapraz eşlemeyi teknik olarak da imkânsızlaştırıyor — spec'in ötesinde sağlam bir uygulama |
| §4.1 (iki katman, transaction bağlamı) | chunk2 §2.2–2.5 | ⚠ | RLS + roller sağlam; ancak bağlam kancasının yaşam döngüsü tanımsız (P-3) ve auth-öncesi sorgular çelişiyor (P-2) |
| §4.2 T-1, T-2, T-5, T-7 | chunk2, chunk4, chunk6 | ✓ | R-1…R-9 testleri, `NotFoundOnDenied`, anonimleştirilmiş dump |
| §4.2 **T-3 (ofis kapsamı), T-4 (sistem yön.), T-6 (agregat)** | — | ❌ | Ofis kullanıcısının çoklu-tenant erişimi ve kapsam-bağımsız agregat okuma RLS modelinde karşılıksız (P-1) |
| §4.3 kabul kriteri (negatif test, app-scope kapalı) | chunk2 R-2, chunk9 §9.1 | ✓ | R-7 superuser kontrolü kritik tuzağı kapatıyor |
| §4.4 (V-1…V-5) | chunk6 §6.5 | ✓ | Mekanizma + "bulunamadı" davranışı tek noktadan |
| §5 (yöntem matrisi, MFA, magic link, A-1…A-6) | chunk5 | ⚠ | İçerik tam; ancak kriter 5 chunk6'ya, kriter 12 chunk7'ye dayanıyor — sıra kırık (P-4); login bootstrap tanımsız (P-2) |
| §6 (Y-1…Y-6) | chunk6 §6.1–6.4 | ✓ | Policy zorunluluğu boot kontrolü + arch testi + `Gate::after` audit |
| §7 (üç log sınıfı, garanti hiyerarşisi, damga, WORM) | chunk3 | ✓ | Hiyerarşi spec ile birebir; sert/gevşek ayrımı testle sabitleniyor (P-6 notu hariç) |
| §8.1 (D-1…D-4), §8.2, §8.3 | chunk4 | ✓ | Tenant-anahtarlı dosya şifreleme, süreli tekil bağlantı, kritik satır silme koruması |
| §8.4 (K-1…K-3) | chunk4 §4.5–4.6 | ✓ | Rıza sürümlü; anonimleştirme scripti prod'da çalışmayı reddediyor |
| §8.5 (analitik sınırı, k=2) | chunk3 §3.8 + chunk8 §8.6 | ✓ | Aggregate'te `user_id` yok; eşik sorgu katmanında; kişi kırılımı parametresi kabul edilmiyor |
| §9 (R-1…R-10) | chunk6 §6.6 | ✓ | Alfabe, çakışma, kapsam dışı "bulunamadı" |
| §10 (kanallar, İYS, teslim, şablon, kademeli, inbound) | chunk7 | ✓ | Sert kapı + İYS-başarısızsa-gönderme; null adaptör + contract doğrulama disiplini doğru |
| §11 (RT-1…RT-6) | chunk8 | ✓ | Yalnız private kanal; beyaz liste yükü; TTL göstergesi |
| §12 dayanıklılık, zaman dilimi | chunk3 §3.3, chunk8 §8.7, chunk1 §1.2 | ✓ | Audit istisnası ↔ dış servis ayrımı testle sabit |
| §12 **arama (Türkçe duyarsız), takvim (tanımlı takvim)** | — | ⚠ | Hiçbir chunk'ta yok; kime bırakıldığı da yazılmamış (P-8) |
| §13 sınır durumları | chunk2/5/6/7/8 dağılımlı | ✓ | Pasif kullanıcı oturumu, eşzamanlılık (dikeye not), davetsiz kod |
| §14 (20 kabul kriteri) | chunk9 §9.2 | ✓ | Birebir eşleme + kriter numaralı test adı — örnek alınacak desen |
| §15 açık noktalar | chunk4 (2 ✓), chunk5 (3, 4 ✓), dikey (1 ✓) | ⚠ | §15/5 (tatil takvimi) hiçbir chunk'ta ele alınmamış; index'in "ilgili chunk'larda yazılıdır" iddiası bu madde için doğru değil (P-8) |
| AD-25/26 (yerleşim, test disiplini) | chunk1, chunk9 | ✓ | Arch testlerinin kasıtlı ihlalle kanıtlanması; `app/Support/` eklemesi doküman güncellemesiyle birlikte planlanmış |
| Sahip kararı (otomatik silme yok) | chunk4 §4.3, kriter 12; chunk9 §9.5 | ✓ | Tarama build kırıyor |

## ✅ What works

- **Deneyle kanıtlanmış varsayım disiplini planın en güçlü yanı.** Superuser'ın `FORCE RLS`'i bypass ettiğinin canlıda gösterilip üç-rol modeline ve R-7 testine bağlanması; boş-string/NULL ayrımının fonksiyona işlenmesi; PgBouncer sızıntı deneyi — bunlar "plan kanundur" iddiasının hakkını veren, yeniden keşif gerektirmeyen kayıtlar.
- **İki test suite ayrımı (RefreshDatabase ↔ truncation) doğru kurulmuş** ve AD-26'nın "SET LOCAL sarmalanmış transaction'da prod davranışını yansıtmaz" tuzağını açıkça çözüyor; `omurga-rls`'in parallel'siz koşması doğru.
- **Soru 3 (log hiyerarşisi): tutarlı.** Üç sınıfın garanti seviyeleri (yazma sert / erişim sert / aktivite gevşek) chunk3 tablosunda, transaction-garanti ↔ damga-async ayrımında (§3.3), chunk4 D-3'te ve chunk9 kriterlerinde aynı; spec §7 ile birebir. Ayrı tablo kararının gerekçesi (hacim/garanti/saklama profili) sağlam.
- **Soru 6 (migration güvenliği): doğrulandı.** Dokuz chunk'ın tamamı tarandı — yalnız CREATE ve kolon ekleme var; kolon silme/yeniden adlandırma/tip değiştirme yok. chunk9 §9.6'nın beyanı doğru.
- **Soru 7 (kabul kriterleri): somut.** Kriterlerin büyük çoğunluğu "kasıtlı ihlalle kanıtlanır", "SQL denemesiyle kanıt", "imza kontrolü" gibi doğrulama yöntemiyle birlikte yazılmış; spec §14 → test adı eşlemesi (`omurga_kriter_07_...`) izlenebilirliği mekanikleştiriyor. Temenni düzeyinde kriter yok.
- **Sahip kararının uygulanması temiz:** chunk4 hiçbir süre-sonu işi tanımlamıyor, kriter 12 ve `kural-tarama.sh` bunun geri gelmesini build seviyesinde engelliyor.
- Rollback bölümünün "RLS'i kapatmak / postgres'e bağlamak rollback değildir" kırmızı çizgisi, deney 1'in operasyonel dersini doğru yere taşıyor.

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

**P-1 (MAJÖR) — Ofis kullanıcısının çoklu-tenant erişimi ve T-6 agregat okuma, RLS modelinde karşılıksız.** RLS deseni tek değerli bağlam varsayar (`tenant_id = app.current_tenant_id()`), middleware "kullanıcıdan tenant belirlenir" der (chunk2 §2.4). Ofis kullanıcısı ise tenant'a bağlı değildir ve kapsamındaki **birden çok** müşterinin verisini görür (spec T-3); SimpleScopeResolver "ofis → tümü" döndürür (chunk6 §6.4). Bu "tümü"nün RLS altında nasıl sorgulanacağı hiçbir chunk'ta tanımlı değil: tenant döngüsüyle mi (T-4'te sistem yöneticisi için ima edilen "tek tek bağlama alma" — ofis için de mi?), ofis bağlamına özel policy şıkkıyla mı, güvenli view'la mı? Aynı boşluk spec **T-6**'yı da kapsıyor: "agregat sayılar tenant kapsamından bağımsız okunur" gereksiniminin plan karşılığı yok — chunk8 analitik sorguları ve backoffice ekranları da hangi bağlamla koşacağı tanımsız. Ofis tarafı ürünün ana kullanıcısıdır; "plan kanundur, yazmıyorsa yapılmaz" ilkesiyle birleşince implementasyon ilk ofis listesinde duracaktır.

**P-2 (MAJÖR) — Kimlik doğrulama öncesi sorguların bağlam bootstrap'i tanımsız.** Bağlam "kimliği doğrulanmış kullanıcıdan" belirlenir; ama kullanıcıyı bulmak için `users` sorgusu gerekir — henüz bağlam yokken. `users` muaf tablo listesinde değil (chunk2 §2.4 örnek listesi: migrations, jobs, cache, mevzuat, parametreler); `beforeExecuting` kancası bağlamsız sorguda exception fırlatır; üstelik users RLS politikasındaki `tenant_id = app.current_tenant_id()` şıkkı bağlamsız değerlendirmede fonksiyonun kendisinin exception'ıyla patlar. Aynı sorun auth öncesi dokunulan her tabloda var: `magic_linkler` (token lookup), `auth_yontem_ayarlari`, `oturumlar`. Login, magic link tıklaması ve oturum çözümlemesi bu planla **çalışamaz**; hangi tabloların hangi gerekçeyle bağlam muafiyeti aldığı (ve muafiyetin RLS tarafındaki karşılığı) açıkça tanımlanmalı. Muaf tablo listesinin "ör." ile açık uçlu bırakılması da aynı sebepten kanun standardını karşılamıyor — liste kesin olmalı.

**P-3 (MAJÖR) — Seçenek B'nin (beforeExecuting) transaction yaşam döngüsü tanımsız; tercih savunulabilir ama tarif "kanun" seviyesinde değil.** (Soru 2'nin cevabı.) Seçenek A'nın reddi gerekçeli ve makul (uzun istek, dış servis çağrısı transaction içinde kalır). Ancak B'nin tarifi yarım: "aktif transaction yoksa transaction açılır ve bağlam set edilir" — bu transaction'ı **kim, ne zaman commit eder?** `SET LOCAL` transaction dışında no-op olduğundan her sorgu fiilen bir transaction'a girmek zorunda; kancanın açtığı transaction kapatılmazsa PgBouncer transaction pooling'de bağlantı rehin kalır ve havuz tükenir. İkinci boşluk: kancanın içinde `SET LOCAL` çalıştırmak da bir sorgudur — kancanın kendisini yeniden tetiklemesine karşı reentrancy koruması tanımlanmamış ("bağlam zaten set edilmişse etmez" bunun tamamı değil; bağlam set **edilirken** tetiklenme ele alınmalı). Explicit `DB::transaction` içindeki işler için model net; onun dışındaki okuma sorguları için açılış/kapanış sözleşmesi yazılmadan bu mekanizma implemente edilemez.

**P-4 (MAJÖR) — chunk5'in kabul kriterleri sonraki chunk'lara dayanıyor; uygulama sırası kırık.** (Soru 1'in cevabı.) chunk5 §5.3 MFA zorlaması "chunk6'daki policy katmanında zorlanır" der ve kriter 5 bunu test eder — ama chunk6 henüz yapılmamıştır. Kriter 12 (A-4 işlemleri "kullanıcıya bildirim gidiyor") chunk7'nin bildirim altyapısına dayanır. "Bir chunk bitmeden sonraki başlamaz + her chunk sonunda kendi kriterleri karşılanmış olmalı" kuralıyla bu iki kriter karşılanamaz. Çözüm mekanik: bu kriterler ilgili chunk'ın kriterlerine taşınmalı (ör. chunk6'ya "MFA'sız kullanıcıda yetki etkisiz" kriteri, chunk7'ye "A-4 bildirimleri") veya chunk5'te arayüz/`Null` implementasyonla sınırlandığı açıkça yazılmalı. Bunun dışında bağımlılık grafiğinde döngü bulunamadı.

**P-5 (minör) — users RLS istisnasının `WITH CHECK` tarafı gereğinden geniş.** (Soru 5'in cevabı.) `USING` tarafındaki `tenant_id IS NULL` şıkkı bilinçli ve savunulabilir: NULL satırlar hiçbir tenant'ın verisi değil, 3E'nin kendi personelidir — tenant→tenant sızıntısı (kırmızı çizgi 1) doğmaz; müşterinin ofis listesini görmemesi policy'ye bırakılmış ve testle sabitlenecek. Ancak aynı şık **WITH CHECK'te de** var: müşteri bağlamındaki bir istek, app-layer atlatıldığında `tenant_id IS NULL` bir satır **yazabilir/güncelleyebilir** — yani ikinci savunma hattı, ofis/sistem satırlarına yazmaya karşı hiç yok. Okuma istisnası gerekliyse bile yazma tarafı daraltılmalı (WITH CHECK yalnız `tenant_id = app.current_tenant_id()`; NULL-tenant satırların yazımı için ayrı, dar bir yol tanımlanmalı — bu da P-1/P-2'deki ofis bağlam modeline bağlanıyor).

**P-6 (minör) — Append-only zorlaması `DO INSTEAD NOTHING` ile sessiz.** RULE, UPDATE/DELETE'i hata vermeden yutar: uygulamada bir bug audit satırını güncellemeye çalışırsa işlem "başarılı" görünür ve fark edilmez. "Değiştirilemez" garantisi sağlanıyor ama gözlemlenebilirlik kaybıyla. Tercih trigger + `RAISE EXCEPTION` olmalı (ihlal denemesi görünür kırılır); sessiz no-op bilinçli tercihse gerekçesi ve izleme yolu yazılmalı. Kabul kriteri 1'in "denenip kanıtlanır" adımı da hatayı değil sessizliği kanıtlayacak — kriter neyi göstereceğini netleştirmeli.

**P-7 (minör) — SimpleScopeResolver borcu işaretli ama sıralama kısıtı yazılmamış.** (Soru 4'ün cevabı.) Arayüz (erişilebilir tenant listesi + izin başına kapsam seviyesi) Spec 3'ün ağaç modeline dönüştürülebilir görünüyor; `teams=false` kararının gerekçesi doğru; "bu sınıfa iş kuralı eklenmez" işareti ve issues.md kaydı yerinde — sökülmeden değiştirilebilirlik makul. Ancak geçici davranış "ofis → tümü"dür: Spec 3 implementasyonundan önce herhangi bir dikey modül gerçek müşteri verisiyle prod'a çıkarsa her satışçı her müşteriyi görür (AD-03'ün satışçı-grubu kapsam kuralı ihlal olur). "TreeScopeResolver gelmeden dikey modül prod'a çıkmaz" kısıtı plana açıkça yazılmalı; yazılmazsa bu borç sessizce dikeylere sızar.

**P-8 (minör) — Spec §12'nin arama ve takvim satırlarının plan karşılığı yok; index'in açık-nokta iddiası §15/5 için tutmuyor.** Türkçe-duyarsız arama (unaccent) ve "süre hesapları tanımlı takvime dayanır; resmî tatil takvimi admin-tanımlı" gereksinimlerinin hangi chunk'ta veya hangi dikey planda karşılanacağı yazılmamış. Özellikle chunk7'nin `SureTakibiTara` motoru gün hesabı yapar — takvim yapısı yokken kademelerin nasıl hesaplanacağı (takvim günü mü, EGT-43 gelene kadar hangi varsayım) tanımsız. Index "spec §15 açık noktalarının ilgili chunk'larda nasıl ele alındığı yazılıdır" der; §15/5 hiçbir chunk'ta geçmiyor — iddia bu madde için yanlış.

**P-9 (not) — chunk8'in chunk3'e fiili bağımlılığı index tablosunda görünmüyor.** §8.6 "veri toplama chunk3 §3.8'de kuruldu" der; bağımlılık satırı "chunk5, chunk6". Numara sırası fiilen koruduğu için pratik risk yok, ancak tablo eksik — belge tutarlılığı için eklenmeli. Benzer şekilde audit tablolarının kendilerinin RLS/muafiyet durumu (tenant-kapsamlı mı, muaf mı) hiçbir chunk'ta beyan edilmemiş — P-1/P-2 çözülürken birlikte netleşmeli.

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

1. Ofis kullanıcısının kapsamlı ama çok-tenant'lı sorguları için seçilen mekanizma ne olacak (P-1): istek başına tek tenant bağlamı + uygulama katmanında birleştirme mi, bağlam değerine "ofis modu" eklemek mi, agregatlar için ayrı güvenli görünümler mi? Karar RLS politika desenini etkilediği için chunk2'ye geri yazılmalı.
2. Auth-öncesi tabloların kesin muafiyet listesi ve muafiyetin RLS karşılığı (P-2) — muaf tablo hâlâ RLS'li mi (policy'siz FORCE RLS her şeyi gizler), yoksa RLS'siz mi?
3. `SureTakibiTara` EGT-43 (takvim) gelene kadar hangi varsayımla çalışacak (P-8)?

## Verdict: ❌ REJECTED

Planın karakteri güçlü: deneyle kanıtlanmış varsayımlar, iki-suite test ayrımı, kriter-test birebir eşlemesi ve sahip kararının tavizsiz uygulanması "plan kanundur" iddiasına yakışıyor; sorulan sekiz noktadan dördü (log hiyerarşisi, migration güvenliği, kabul kriterlerinin somutluğu, ScopeResolver arayüzünün değiştirilebilirliği) temiz veya notla temiz çıktı. Ancak tam da "kanun" iddiası yüzünden dört majör boşluk bloklayıcı: ofis kullanıcısının çoklu-tenant erişim modeli ve T-6 agregat okuma RLS deseninde karşılıksız (P-1), auth-öncesi sorgular bağlam zorunluluğuyla çelişiyor ve login fiilen çalışamaz (P-2), seçilen bağlam kancasının transaction yaşam döngüsü tanımsız (P-3), chunk5'in iki kabul kriteri sonraki chunk'lara dayanıyor (P-4). Bunlar "implementasyon duruma göre çözer" denebilecek detaylar değil — planın kendi kuralıyla implementasyonun durup soracağı noktalardır ve şimdi cevaplanmaları planın varlık sebebidir. P-5…P-9 küçük ve çoğu majörlerin çözümüne bağlanıyor. Majörler kapatılıp re-review istendiğinde planın geri kalanı sağlam görünüyor.
