# Review — Potansiyel müşteri — implementasyon planı (v1.1) — 2026-10-19 (re-review)

> **Review type:** plan-re-review
> **Previous review:** `docs/reviews/review-potansiyel-musteri-plan-2026-10-19.md` (heyet 809, REJECTED, F-1…F-6)
> **Upstream document(s) read:** `proje-haritasi.md` §4.4 (dokuz madde + iki yeni Sets kararı: vergi no uyarıdır/engel değildir — Vitsan gerekçesi; temas telefonu açık + erişim logu + müşteri kartına aynı kural), YouTrack EGT-216 (önceki reviewda okundu, bu turda değişmedi), `docs/arch/mimari-kararlar.md` (AD-04, AD-05, AD-06, AD-08, AD-11, AD-16, AD-21, AD-23, AD-25, AD-26), `CLAUDE.md`. Spec turu yoktur (Sets kararı, 19.10.2026).
> **Other cross-referenced documents:** v1.1'in genişlettiği «Doğrulanmış mevcut durum» satırları tek tek koddan açıldı: `config/tenancy.php:90-253` (`exempt_tables`, `sales_meetings`/`record_notes` blokları), `TenantContextGuard.php:160-175`, `tests/Arch/TenancyTest.php:32-45` (satır 42'nin yalnız yorumda geçtiği, asıl test satır 32'de olduğu doğrulandı — brief'teki düzeltme doğru), `CloseMeeting.php` (tam dosya, satır 60-110 dahil), `Masker.php:28-40`, `config/audit.php:141-156`, `AccessLogger.php:20-52`, `CustomerPeople.php:59-95` (`'phone' => $person->phone,` satırının loglanmadığı, `TenantContext::run` içinde olduğu teyit edildi), `AddCustomerNote.php:36-41`, `RecordNotePolicy.php:36-53`. Önceki review'ın kendi kod kanıtları (`TenantContext.php`, `SessionScopeResolver.php`, `CreateCustomerPerson.php`, `AssignCustomersToUser.php`, `app.all_customer_ids()` migration'ı, `design/32`) bu turda tekrar açılmadı — önceki review'da doğrulanmış ve v1.1'de değişmemiş.
> **Stage locks in effect:** Sets'in dokuz önceki kararı artı bu turun iki yeni kararı (vergi no uyarı, temas telefonu açık+loglu) kilitli — yeniden tartışılmadı. Yalnız planın bu kararlara ve mimariye uyumu değerlendirildi.

---

## Upstream Coverage Matrix

(v1.1'in kapsadığı üst maddeler üzerinden sıfırdan üretildi; önceki review'ın matrix'i kopyalanmadı.)

| Upstream § / madde | Reviewed doc'ta durumu | Kanıt |
|---|---|---|
| Harita §4.4 (yeni): «Vergi no eşleşmesi uyarıdır, engel değildir; ad yazılır, kapsam dışına da» | ✓ | K-13 yeniden yazılmış; `app.customers_with_tax_number(text) RETURNS SETOF text` yalnız ad döner (id/durum yok); `TaxNumberRegistry::matchesFor` `href` yalnız kapsam içindekilere; Görev 3/5/6 testleri Vitsan senaryosunu (iki ad birden) kapsıyor |
| Harita §4.4 (yeni): «Temas telefonu açık, sayfa açılışı erişim loguna; aynı kural müşteri kartına» | ✓ | K-22 (`PotentialCustomerCard`, yalnız telefonu olan temas varsa), K-23 + Görev 5b (`CustomerPeople::for()` içine `AccessLogger::record`), iki ayrı test grubu (`satis`, `musteri`) |
| F-1 (önceki review): exempt_tables mekanizması eksikti | ✓ | K-3'e mekanizma cümlesi eklendi, Görev 2 adım 4 `config/tenancy.php` değişikliğini görev olarak taşıyor, dosya haritasında `config/tenancy.php` «Değişen» satırı var, Görev 2 testi `TenancyTest`'in iki modeli muaf sınıflandırdığını ve bağlamsız sorgunun patlamadığını doğruluyor |
| F-2 (önceki review): `CloseMeeting` potansiyel konusu | ✓ | K-15 genişletildi, Görev 4 adım 5 iki dalı (müşteri yolu aynen, potansiyel yolu yeni) tek metotta tarif ediyor, kapanış notu sırası (önce not, sonra durum) korunmuş, test listesi tamamla/iptal + mevcut müşteri testinin değişmeden geçmesini kapsıyor |
| F-3 (önceki review): Masker tam-ad / substring karışıklığı | ✓ | «Doğrulanmış mevcut durum» satırı düzeltilmiş (`Masker.php:28-40` doğru okunmuş), K-24 anahtarların tam olarak `phone`/`email` olması gerektiğini kural olarak koymuş, Görev 3 metni «iç içe dizi konmaz» diyor ve test maddesi var |
| F-4 (önceki review): dönüşümdeki ikinci kontrol kendini dışlamıyordu | ✓ | K-13'ün kendisi artık «kaydın kendisi hariç» diyor, Görev 5 adım 2 açıkça `POTANSİYELİN KENDİSİ HARİÇ (K-13, F-4)` yazıyor, Görev 5 testinde «uyarısız dönüşür» maddesi var |
| F-5 (önceki review): not yetkisi iki yerde farklı anlatılmıştı | ✓ | K-21 yeni, `PotentialCustomerPolicy::note` tek yetki noktası, rota tablosu `.notes.store` artık `note` yeteneğini kullanıyor (önceki `view` tutarsızlığı giderildi), test maddesi `quote.view`'suz kullanıcıyı özellikle sınıyor |
| F-6 (önceki review): hassas okuma (AD-21) kararı yoktu | ✓ | K-22/K-23 + Görev 5b; iki okuma noktası da (potansiyel kart, `CustomerPeople`) kapsanmış, e-postanın `sensitive_fields`'te olmadığı için loglanmayacağı açıkça yazılmış |
| AD-21 (erişim logu, "tek sınır: sensitive_fields listesi") | ⚠ | K-22 doğru okuma noktasını buluyor ama `AccessLogger`'ın tenant çözümleme yolu (`tenantOf` → `CurrentActor::tenantId()` → ambient `TenantScope`) tenant'sız bir `PotentialCustomer` subject'iyle yanlış müşteriye yazabilir — F-7 |
| AD-16 / kırmızı çizgi 13 (maskeleme + audit transaction) | ✓ | K-24 + Görev 3 testi; `Masker::apply` davranışı artık doğru tarif edilmiş |
| AD-23 (own/group/all, seviye daraltması) | ✓ | değişmedi, önceki turda doğrulanmıştı, v1.1 bu kararlara dokunmamış |
| AD-25 (kod yerleşimi) | ✓ | dosya haritası adres sözleşmesine uygun kalmaya devam ediyor; `CloseMeeting` ve `CustomerPeople` değişiklikleri de doğru katmanlarda (Action, Query) |
| AD-26 (test grupları, FrozenClock, RLS testi uygulama rolüyle) | ✓ | `CustomersWithTaxNumberTest` yine `omurga-rls` grubunda, uygulama rolüyle; yeni testler ilgili gruplara (`satis`, `musteri`) doğru dağıtılmış |
| CLAUDE.md kırmızı çizgi taraması (plan seviyesinde, 17 madde) | ✓ | 1/2/4 hâlâ yapısal olarak konu dışı (K-3 + exempt mekanizması); 13 artık hem yazma (maskeleme) hem okuma (erişim logu) tarafını kapsıyor; diğerleri önceki turdan değişmedi |

---

## ✅ What works

- **Altı bulgunun altısı da gerçek düzeltmeyle kapatılmış**, kozmetik yama değil: F-1'in mekanizması somut dosya + somut test adımı olarak Görev 2'ye girmiş; F-2 iki yolu (müşteri/potansiyel) tek konu-çözücü metotta tutarak "iki dal ayrı kopyalanmaz" diye kendi tekrarını da önlemiş; F-3'ün düzeltmesi yalnız yorum değil, «Doğrulanmış mevcut durum» satırının kendisi düzeltilmiş — bir sonraki okuyucu da yanlış mekanizmayı miras almayacak.
- **F-4'ün çözümü iki ayrı yerde tutarlı:** hem K-13'ün genel kuralı («kaydın kendisi hariç») hem Görev 5'in adım adım akışı aynı cümleyi taşıyor, kendini-dışlama hem edit hem convert akışında test edilmiş.
- **Yeni Sets kararı (vergi no = uyarı) gerçek bir vaka ile gerekçeli** (Vitsan: tek çatı firma, üç alt firma) ve fonksiyonun tasarımı (`RETURNS SETOF text`, yalnız ad, id/durum yok) açık eşleşmenin bilgi sızdırma riskini orantılı şekilde sınırlıyor — F-4'ün çözdüğü kendini-eşleştirme tuzağı bu yeni kuralla da tutarlı kalmış.
- **F-6'nın çözümü isabetli bir ek fark yakalamış:** sorun yalnız yeni ekranda değil, mevcut `CustomerPeople.php:88`'de de varmış; plan bunu ayrı bir görev (5b) olarak scope'a almış ve Sets'in «bu işte düzelt» talimatını doğru yorumlamış — aynı tarama iki gerçek kullanıcıyı (müşteri kartı + teklif ekranı) da kapsıyor.
- **F-5'in çözümü dar ve adres sözleşmesine uygun:** `PotentialCustomerPolicy::note` tek karar yeri, müşteri/teklif tarafının kuralı dokunulmamış — regresyon riski yok.

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

**F-7 — K-22'nin `AccessLogger::record($prospect, ['phone'], …)` çağrısı, dar kapsamlı bir satışçı için erişim logunu yanlış müşteriye yazabilir.** `AccessLogger::tenantOf(Model $subject)` (`AccessLogger.php:117-120`) önce `$subject->getAttribute('tenant_id')`'a bakar; `PotentialCustomer`'da böyle bir kolon yok (K-3, veri modeli), bu `null` döner ve kod `CurrentActor::tenantId()`'ye düşer. `CurrentActor::tenantId()` (`CurrentActor.php:78-86`) açık bir `TenantContext` yoksa `ambientScope()`'a bakar — bu da `EnsureTenantContext` middleware'inin **her istekte** (rota `tenancy.context_free_routes`'ta değilse — potansiyel müşteri rotaları için plan böyle bir muafiyet tanımlamıyor) bağladığı `TenantScope`'tur: imzalı kullanıcının **erişebildiği tüm müşteriler**. Tenant sayısı tam **1** ise (`count($scope) !== 1` → `return $scope[0]`), fonksiyon o tek müşterinin id'sini döner. Sonuç: yalnız tek bir müşteriye atanmış bir satışçı (K-4'ün `view.own` senaryosu — tam da bu plan için yazılan reach testinin fixture'ı) bir potansiyel müşteri sayfası açtığında, erişim log satırı **o potansiyel müşteriyle hiçbir ilgisi olmayan atanmış müşterinin `tenant_id`'siyle** yazılır. Tablo append-only olduğu için (AD-06) bu yanlış etiket geri alınamaz ve "kim neye baktı" sorusunu (AD-21'in var oluş amacı) yanlış cevaplar. Plan bu çağrıyı doğrudan `app/Queries/Office/PotentialCustomerCard.php`'a yerleştiriyor ama `AccessLogger`'ın tenant'sız bir subject'le bu şekilde davranacağını hiç doğrulamamış/tartışmamış — aynı sınıfın tenant varsayımlarının kontrolsüz miras alınması, önceki review'ın F-1'inin bulduğu hata biçiminin bir benzeri.

Not: F-7 dışında yeni bir ❌/⚠ üretmedi; matrix'teki tek ⚠ satır (AD-21 context şekli) F-7'nin karşılığıdır.

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

1. (F-7 ile bağlı) K-22'nin erişim-log satırı `tenant_id`'yi ne taşımalı? İki olasılık var ve plan ikisinden birini seçmemiş: (a) her zaman `NULL` (K-3'ün "ofisin kendi olayı" çerçevesiyle tutarlı — ama bunun için `AccessLogger::record`'a tenant'ı zorlayan bir çağrı biçimi ya da `tenantOf()`'un `PotentialCustomer` için özel davranışı gerekir), (b) hiç yazılmaz çünkü bu okuma zaten audit kapsamı dışıdır. Bugünkü `CurrentActor::tenantId()` davranışıyla devam edilirse üçüncü, istenmeyen bir sonuç çıkıyor: dar kapsamlı okuyucuda yanlış müşteriye yazma. Hangisi doğru Sets'e sorulmalı.
2. Görev 5b'nin (`CustomerPeople` değişikliği) EGT-216 kapsamına neden dahil edildiği, planın kendi "Kapanış adresi verilen, bu turda yapılmayanlar" tablosunun simetriği olarak bir cümleyle not edilsin — Sets zaten onaylamış (proje-haritası §4.4 satır 882), bu bir eksik onay değil, yalnız gelecekte planı okuyanın "neden bu ticket CustomerPeople'a dokunuyor" sorusunu tekrar sormaması için.

## Previous Findings Status (re-review only)

| Bulgu | Durum |
|---|---|
| F-1 — exempt_tables mekanizması eksik | ✅ Resolved |
| F-2 — `CloseMeeting` potansiyel konusu tanımsız | ✅ Resolved |
| F-3 — Masker mekanizması yanlış aktarılmış | ✅ Resolved |
| F-4 — dönüşümde kendini-dışlama eksik | ✅ Resolved |
| F-5 — not yetkisi iki yerde farklı | ✅ Resolved |
| F-6 — hassas okumanın erişim logu kararı yok | ✅ Resolved (ama bkz. F-7 — çözümün kendi detayında yeni, dar bir boşluk) |

Düzeltme yeni bir problem yarattı mı: evet, ama küçük ve dar (F-7) — çözümün doğrultusunu bozmuyor, mekanizmanın bir ayrıntısını (tenant'sız subject'in `AccessLogger` içindeki davranışı) atlıyor. Planın ana hedefinden sapma yok; F-8 bir öneri/soru, blocker değil.

## Verdict: ❌ REJECTED

Önceki altı bulgunun hepsi gerçek ve doğru çözülmüş — bu v1.1'in büyük başarısı, kozmetik değil yapısal düzeltmeler. Ancak çözümlerden biri (F-6 → K-22/K-23) kendi içinde yeni, dar bir boşluk bırakıyor: `AccessLogger::record`'ın tenant'sız bir `PotentialCustomer` subject'iyle davranışı plan tarafından hiç doğrulanmamış (F-7). Bu, önceki review'ın tam da F-1'de yakaladığı türden bir hata biçimidir — bir altyapı sınıfının varsayımlarının yeni bir çağıran tarafından sınanmadan miras alınması — ve aynı titizlikle bir cümlelik doğrulama ister. F-8 blocker değil, açık soru olarak taşındı.

```
docs/reviews/review-potansiyel-musteri-plan-re-review-2026-10-19.md
```
