# Re-Review — Plan: Platform Omurgası v2.1 (EGT-21) — 25.07.2026

> **Review type:** plan-review (re-review, F1…F8 kapanış doğrulaması + etkileşim analizi)
> **Reviewed:** `git diff a4a7f4a..d0219a2` (index, chunk2, chunk3, chunk5, chunk6, chunk9) — chunk1/4/7/8 değişmedi, v2.0 okuması geçerli
> **Upstream (kilitli):** spec v1.7 APPROVED · AD-01…AD-28
> **Previous review:** `review-omurga-plan-2026-07-25-r2.md` (v2.0 REJECTED, F1…F8)
> **Stage locks in effect:** ⛔ sahip kararı: otomatik silme/anonimleştirme yok (uygulanmış, bulgu edilmedi)

---

## F1…F8 kapanış doğrulaması

| Bulgu | Doğrulama | Durum |
|---|---|---|
| F1 — `WITH CHECK` DELETE'i korumuyor | Komut bazlı dört politika (§2.4); **canlıda hem tuzak hem düzeltme denenmiş** (deney 8: tek politikayla DELETE geçti; deney 9: dört politikayla `write needs single tenant`); R-5b/R-5c testleri; `TenantPolicy::apply()` altı ifade; kriter 6 "tek birleşik politika kullanılmamış" şema kanıtı | ✅ KAPANDI |
| F2 — ofis kapsam listesi tavuk-yumurtası | §2.8.1: nihai kaynak bağlamsız org tabloları; geçici `app.all_customer_ids()` dar SECURITY DEFINER, S-9'da "geçici" işaretli; çelişkili `customers` satırı düzeltildi | ✅ KAPANDI |
| F3 — ofis olaylarının audit tenant durumu | §3.6 NULL disiplini (N-1…N-3), üç tabloda nullable, policy SQL yazılı, kriterler 11/11b/11c + chunk6 7d | ✅ KAPANDI (ancak yeni mekanizmanın kendisi iki yeni bulgu üretti: N2, ve N1'in kriter çelişkisi) |
| F4 — `magic_links` + istek yaşam döngüsü | §2.8.2 `magic_link_lookup` (altı alan, işleme-ait istisna, S-9); §2.5.1 `EnsureTenantContext` (kapsam hesaplar, **transaction açmaz**, kesin muaf rota listesi, oturum-tenant uyuşmazlığında sonlandırma + audit); R-16/R-17 | ✅ KAPANDI |
| F5 — iç içe semantik | Dört maddelik kesin semantik: alt küme zorunlu (`TenantScopeWideningException`), daraltmada `SET LOCAL` yeniden, çıkışta `finally` ile geri yükleme; R-11b | ✅ KAPANDI |
| F6 — chunk5 kriterleri ileri bakıyordu | U-1 ve step-up kriterleri chunk6 7b/7c'ye taşındı; chunk5'te kalan U-2 kriteri haklı gerekçeyle kaldı (veriden türer, izin gerektirmez) | ✅ KAPANDI |
| F7 — mali bağlantı mekaniği | `pgsql_financial` bağlantı satırı; bekçi kanca iki bağlantıya birden | ✅ KAPANDI |
| F8 — tanımsız middleware + etiket | `EnsureTenantContext` §2.5.1'de tanımlı; chunk7 §7.4 referansı artık karşılıklı tutarlı; yanlış etiket kaldırıldı | ✅ KAPANDI |

## Sorulan etkileşim analizi: yeni tutarsızlık üretildi mi?

**Evet — üç kesişimde dört yeni bulgu.** Sekiz düzeltmenin kendisi doğru; sorunlar düzeltmelerin **ikinci derece etkileşimlerinde**.

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

**N1 (ORTA) — Audit tablolarında UPDATE/DELETE politikasının yokluğu, P-6'nın kovduğu sessiz no-op'u geri getiriyor; kriter 2 bu şemayla kanıtlanamaz.** §3.6'nın yeni policy setinde UPDATE/DELETE politikası bilinçli olarak yok ("append-only trigger zaten reddediyor"). Ancak PostgreSQL RLS semantiğinde, RLS etkin bir tabloda bir komut için **hiç politika yoksa default-deny uygulanır: komut hata vermez, 0 satırla eşleşir**. Yani `UPDATE audit_writes ...` denemesi satır bulamaz → **BEFORE UPDATE trigger'ı hiç ateşlenmez** → ihlal denemesi yine sessizleşir. Bu, chunk3 §3.2'nin gerekçesiyle ("gözlemlenebilirlik kaybı kabul edilmez") ve kriter 2'nin kanıt yöntemiyle ("UPDATE/DELETE **exception** üretiyor — SQL denemesiyle kanıt") doğrudan çelişir: deneme exception değil "0 rows" üretecek. Çözüm: `audit_writes`/`audit_access` için `FOR UPDATE USING (true)` ve `FOR DELETE USING (true)` politikaları eklenir — satır policy'den geçer, trigger `RAISE` eder, ihlal görünür kırılır. (Deney 8/9 disipliniyle canlıda doğrulanması önerilir.) `audit_activity`'de mevcut kurgu doğrudur: trigger yok, retention chunk-drop policy'ye tabi değil, kullanıcı DELETE'inin default-deny ile sessizce 0 satır olması kabul edilebilir.

**N2 (MAJÖR) — Ofis alanı audit yazımı bekçiye takılır; kriter 11c mevcut mekanizmayla çalışamaz.** N-1 disiplini ofis olaylarının (ofis girişi dahil) `tenant_id = NULL` ile `audit_writes`'a yazılmasını öngörüyor ve kriter 11c bunu sınıyor. Ama iki engel tanımsız: (a) `audit_writes` tenant-kapsamlı tablo sınıfındadır ve **bekçi kanca "aktif `TenantContext` olmadan tenant-kapsamlı tabloya sorgu → exception"** der — ofis girişinde `TenantContext` yoktur; NULL-audit INSERT'i bekçiye takılır. (b) INSERT politikası `tenant_id IS NULL OR tenant_id = app.write_tenant_id()` — SQL'de OR alt-ifadelerinin değerlendirme sırası **garantili değildir**; bağlamsız oturumda `write_tenant_id()` değerlendirilirse "tenant context not set" exception'ı NULL satırın yazımını da düşürebilir. Çözüm yönü kararı gerekiyor: ya audit tabloları bekçide dar bir "NULL-yazım" muafiyeti alır ve INSERT politikası bağlam-güvenli yazılır (ör. `tenant_id IS NULL` şıkkını ayrı bir `FOR INSERT` politikasına ayırmak — PostgreSQL'de aynı komut için birden çok permissive politika OR'lanır ve her politika bağımsız değerlendirilir), ya da ofis istekleri için açık bir "ofis bağlamı" tanımlanır. Hangisi seçilirse §2.5 bekçi tanımı, §3.6 SQL'i ve kriter 11c birlikte güncellenmeli.

**N3 (MAJÖR) — `customers`'a dört politikanın uygulanması yeni müşteri yaratmayı imkânsızlaştırıyor.** §2.8: "customers özel durumu: §2.4'ün dört politikası `id` üzerinden kurulur." O zaman INSERT politikası `WITH CHECK (id = app.write_tenant_id())` olur — yeni müşterinin `id`'si INSERT anında üretilir ve **hiçbir mevcut kapsam listesinde olamaz**; ofis onboarding akışı (yeni firma kaydı) DB katmanında kalıcı olarak reddedilir. v2.0'daki tek-politika biçiminde de aynı sorun vardı; dört politikaya genelleşince görünür hale geldi. Çözüm: müşteri oluşturma için dar bir yol tanımlanmalı — ör. `app.create_customer(...)` SECURITY DEFINER fonksiyonu (S-9 envanterine girer) veya `customers`'a ofis-alanına özgü ayrı bir INSERT politikası; UPDATE/DELETE/SELECT politikaları mevcut haliyle kalabilir (UPDATE'in `forWrite` gerektirmesi ve silmenin fiilen imkânsızlığı AD-23'ün "tenant silinmez, pasifleştirilir" kuralıyla uyumludur — bu yan etki olumlu ve kayda geçmeye değer).

**N4 (ORTA) — Geniş kapsamlı okuma sırasında erişim logu yazımı tek-tenant `WITH CHECK`'e takılır.** Erişim logu okuma anında yazılır ve garantisi serttir (§7.2: log yoksa görüntüleme yok). Ofis kullanıcısı çok-tenant'lı kapsamla (`run([1..10])`) müşteri X'in hassas alanını görüntülediğinde `audit_access` satırı `tenant_id = X` ile yazılır; INSERT politikası `X = app.write_tenant_id()` ister, kapsam 10 elemanlıdır → exception → sert garanti gereği **görüntüleme engellenir**. Sonuç: ofis geniş okuma modunda hiçbir hassas alanı görüntüleyemez. F5'in iç içe daraltma semantiği bunu çözmeye yeter — `AccessLogger`'ın log satırını ilgili kaydın tenant'ına `forWrite($x)` iç bağlamıyla yazdığı açıkça yazılmalı (chunk3 §3.5'e bir cümle + test). N2'deki çözüm politikaları da bu durumu birlikte ele almalı.

## ✅ What works

- **Sekiz bulgunun sekizi de gerçek içerikle kapandı** — özellikle F1'in kapanışı örnek nitelikte: bulgu önce canlıda yeniden üretilmiş (deney 8), düzeltme canlıda doğrulanmış (deney 9), test ve şema kanıtı eklenmiş. "Kanıtlanmış varsayım" disiplini bu planın en güçlü kası olmaya devam ediyor.
- §2.5.1 istek yaşam döngüsü, F4'ün istediği kanun netliğinde: middleware'in transaction açmama gerekçesi P-3'e bağlanmış, oturum-tenant uyuşmazlığı davranışı (sonlandır + audit) yazılmış, muaf rota listesi kesin.
- F5'in dört maddelik iç içe semantiği (`finally` ile geri yükleme dahil) ofisin "geniş oku → daralt → yaz → dön" desenini nihayet tam tanımlıyor — ve N4'ün çözümüne de zemin hazırlıyor.
- `app.all_customer_ids()`'in S-9 envanterinde "geçici" işaretlenmesi ve envanterden düşürülene kadar tarama uyarısı üretmesi, geçici borcun unutulmasını yapısal olarak engelliyor.
- Revizyon geçmişi tablosu bu kez gerçek durumla birebir örtüşüyor (tamamı diff'e karşı doğrulandı).

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

1. N2'nin yön kararı: audit NULL-yazımı için bekçi/policy muafiyeti mi, açık "ofis bağlamı" mı? (Seçim §2.5, §3.6 ve kriter 11c'yi birlikte etkiler.)
2. N3'ün yolu: `app.create_customer()` SECURITY DEFINER mı, ofis-alanına özgü INSERT politikası mı? (İlki S-9 envanterine girer; ikisi de kanuna yazılmalı.)

## Verdict: ❌ REJECTED

v2.1, v2.0'ın sekiz bulgusunun tamamını yüksek kaliteyle kapatıyor — F1'in canlı deneyle çift yönlü kanıtı bu zincirin en iyi pratiği. Ancak sorunun tam da sorduğu yerde, düzeltmelerin ikinci derece etkileşimlerinde dört yeni bulgu doğdu: audit tablolarında politika yokluğunun default-deny yoluyla P-6 sessizliğini geri getirmesi ve kriter 2 ile çelişmesi (N1), ofis NULL-audit yazımının bekçiye ve `write_tenant_id()` değerlendirme riskine takılıp kriter 11c'yi çalışamaz bırakması (N2), `customers` INSERT politikasının yeni müşteri yaratmayı imkânsızlaştırması (N3) ve geniş kapsamlı okumada erişim logu yazımının tek-tenant `WITH CHECK`'e takılması (N4). Dördü de aynı mekanik ailenin (komut-bazlı politika × bağlam modeli) uç durumları; N1/N4 dar düzeltmeler, N2/N3 yön kararı istiyor. Kapatıldıklarında planda başka engel görünmüyor — deney 8/9 disipliniyle (düzeltmeleri canlıda doğrulayarak) kapatılmaları önerilir.
