# Audit zinciri ekranı ve bağımsız doğrulama aracı — spec

*10.08.2026 · Sets'in tanımı (EGT-92 tasarım oturumu) · AD-06 (audit zinciri ve dış tanık), AD-11 (kritik kayıt silinmez), AD-16 (hassas değer maskeleme), AD-21 (erişim logu ve kişi bazlı izleme yasağı), AD-23 · plan `omurga-plan.md` chunk3 §3.8*

Bu belge backoffice'e eklenecek **Audit zinciri** ekranının ve onun ürettiği **bilirkişi paketinin** tek kaynağıdır. Her satır ya Sets'in 10.08.2026 tarihli tanımından ya da referansı verilen mimari karardan gelir; varsayımla doldurulmuş satır yoktur.

**Kapsam dışı:** zincirin kendisi (kurulu ve çalışıyor — `SealAuditBatch`, `PublishAuditRoots`, `VerifyAuditChain`, `ArchiveAuditBatch`), anchor deposunun kurulumu (`docs/issues.md`), hukuki delil değerlendirmesi (EGT-51'de "bizim işimiz değil" diye düşürüldü).

---

## 1. Neden var

AD-06'nın "delil" iddiasını ayakta tutan şey, zincirin **uygulama kodu kullanılmadan** yeniden hesaplanabilmesidir. Prosedür yazılı ve 26.07.2026'da fiilen kanıtlandı: psql + Python ile kök, uygulama kodu olmadan yeniden hesaplandı ve birebir eşleşti. Anchor deposu (`Sets/3eegitim-anchor`) public, force-push ve silme engelli, ilk mühür `eb18642` ile yayımlandı.

**Eksik olan:** paketlenmiş, dışarıdan çalıştırılabilir, **bilirkişiye teslim edilebilir** bir araç. Bugün bir bilirkişiye *"şu README'yi okuyun ve psql ile kendiniz hesaplayın"* demek zorundayız.

⚠ **Sets'in uyarısı (10.08.2026):** *"tool koymak tüm sistemi şeffaflaştırmak demek"* — bu yüzden ekran iki katmanlıdır ve iki katman farklı izleyiciye bakar:

| Katman | Kim okur | Ne yapar |
|---|---|---|
| **Vitrin** | zinciri okuyabilen (`audit.view_chain`) | zincirin durumu, mühür anatomisi, log yapısı, mekanizmanın anlatımı |
| **Araç** | doğrulama yetkisi olan (`audit.verify`) | doğrulama koşturur, delil paketi indirir |

⛔ **Sunucuda bizim kodumuzla koşan doğrulama, AD-06'nın iddiasını kanıtlamaz — tekrarlar.** Ekrandaki canlı doğrulama günlük kullanım içindir; delil değeri **pakettedir**, çünkü paket bizden bağımsız çalışır.

---

## 2. Adres, yetki, menü

- Backoffice paneli, `navigation.groups.audit` grubu — `İşlem günlüğü` (`WriteLog`) ve `Erişim günlüğü` (`AccessLog`) yanında üçüncü sayfa.
- **Görmek:** yeni **`audit.view_chain`** izni.
- **Doğrulama koşturmak ve paket indirmek:** yeni **`audit.verify`** izni.

⚠ **Düzeltme (10.08.2026, koda karşı ölçüldü).** Tasarım oturumunda vitrin için `audit.view` denmişti; o izin **ofis ailesine** aittir (`config/authorization.php:111`, `office` bloğu) ve bu ekran backoffice'tedir. Backoffice ailesinin mevcut audit izinleri `audit.view_all_tenants` ve `audit.view_access_log`'dur; ikisi de **satır içeriği** okumaya dairdir ve zincir ekranından daha hassastır — mühür özeti kimseye ait değildir, kapsadığı şeyi ele vermez. Mevcut birini ödünç almak, daha az hassas bir ekranı daha dar bir kapının arkasına koymak olurdu. Bu yüzden iki yeni izin: `audit.view_chain` ve `audit.verify`. İkisi de `config/authorization.php`'nin `backoffice` listesine, ilgili şablonlara `PermissionTemplateSeeder` üzerinden girer.

Ayrı izin olmasının sebebi ikisi de ölçülebilir: paket **dışarı çıkan bir çıktıdır**, ve tam zincir doğrulaması **maliyetli bir koşudur**. Zincirin durumunu okuyabilmek, bu ikisine karar verebilmek anlamına gelmez.

---

## 3. Ekran kompozisyonu

Tasarım oturumunda üç yerleşim karşılaştırıldı; seçilen **iki kolonlu, ikisi de dolu** olan (v3-B). Boşluk, listenin az sütunlu olmasından geliyordu — tanık ve arşiv durumunu listede görmek zaten kullanışlı.

### 3.1 Üst şerit — dört kart

| Kart | İçerik | Kaynak |
|---|---|---|
| **Zincir durumu** | ✓ Sağlam / ⚠ Bulgu var + son doğrulama zamanı | son doğrulama koşusu |
| **Mühür** | toplam sayı + ilk mühür tarihi → bugün | `audit_stamps` |
| **Tanıkta** | tanık referansı olan mühür sayısı + "eksik yok" / "N eksik" | `witness_reference` |
| **Arşivde** | arşivlenmiş mühür sayısı + "N bekliyor" | `archived_at` |

### 3.2 Otuz günlük şerit

Her sütun bir gün, renk o günün doğrulama sonucu. Zincirin sağlığı bir andaki değer değil, bir çizgidir: dünkü yeşilin bugünkü kırmızıyı bağlamına oturtması gerekir.

### 3.3 Sol kolon — mühür listesi

Sütunlar: **tarih · tablo · satır aralığı · adet · kök · tanık commit · arşiv rozeti**. Satır seçimi sağ kolonu doldurur.

### 3.4 Sağ kolon — dikey üç blok

1. **Seçili mühür künyesi:** `merkle_root`, `previous_chain_hash`, `chain_hash`, `hash_algorithm`, satır aralığı (`from_id` → `to_id`), tanık commit (bağlantılı), arşiv yolu ya da "bekliyor". Altında `[Bu mührü doğrula]`.
2. **"Bu kök nasıl doğdu":** seçili mührün gerçek sayılarıyla küçük Merkle ağacı + yaprak kuralının tek cümlelik özeti (*"yaprak = satırın HER kolonu, uzunluk önekli, saat UTC"*).
3. **Log yapısı** — üç tablo ve mühürlenip mühürlenmediği, **gerekçesiyle**:

   | Tablo | Ne tutar | Mühür |
   |---|---|---|
   | `audit_writes` | işlem — **giriş/çıkış dahil** | ✓ mühürlenir |
   | `audit_access` | hassas kayıt okuması | ✓ mühürlenir |
   | `audit_usage` | ekran telemetrisi (route, filtre, sonuç sayısı) | ✗ mühürlenmez, **ama silinmez** |

   ⚠ Bu üçüncü satır ekranda **gerekçesiyle** durur, çünkü *"neden mühürlenmiyor"* sorulan bir sorudur (Sets, 10.08.2026). Üç sebep: (a) yazma güvencesi gevşektir — `UsageLogger` hatayı yutar, sayfa yine render edilir; satırı eksik kalabilen bir tabloyu mühürlemek zincire tutamayacağı bir "tam kayıt" iddiası yükler; (b) AD-21 kişi bazlı kullanım izlemeyi yasaklar ve backoffice bu tabloyu yalnız günlük agregattan okur — mühürlemek onu satır satır okunacak bir delile dönüştürür; (c) hacim. **"Sisteme ne zaman girmiş" sorusu `audit_writes`'tadır** (`auth.login` / `auth.logout` / başarısız deneme, `ip_address` ve `user_agent` ile) ve o tablo mühürlüdür.

   ⛔ Mühürlenmemek silinmek değildir: 07.08.2026 kararıyla hiçbir log zamanlanmış olarak silinmez, `audit_usage` dahil.

### 3.5 Alt şerit

`[Bu mührü doğrula]` · `[Tüm zinciri doğrula]` · `[Seçili aralığı indir]` · `[Tüm zinciri indir]`

### 3.6 Mekanizma sekmesi

Uzun anlatım burada, tam genişlikte: satır → yaprak → ağaç → kök → zincir → tanık → arşiv. Günlük kullanan kişi hiç açmaz; bilirkişiye *"şu sekmeyi açın"* denir.

---

## 4. Doğrulama

| Kapsam | Nasıl |
|---|---|
| **Tek mühür** | anında koşar, sonucu sağ kolonda |
| **Tüm zincir** | kuyruğa atılır (`VerifyAuditChain`), sayfa Livewire ile ilerlemeyi tazeler |

⚠ Tam koşu kuyruğa gitmek **zorundadır**: her mührün kökü satırlardan yeniden hesaplanır, bu milyonlarca satırın hash'idir — saniyeler değil dakikalar. Düğmeye basan kişi bir şeyin ilerlediğini görmelidir, yoksa tekrar basar.

Doğrulama üç kökü karşılaştırır ve **hangisinin uyuşmadığını söyler**: kaydedilen kök · satırlardan yeniden hesaplanan kök · tanıktaki kök. Bu ayrım teşhisin kendisidir — satırlar mı değişti, mühür mü, yoksa yalnız yayımlanan kopya mı.

---

## 5. Delil paketi

**İki ayrı düğme** (Sets, 10.08.2026): `[Seçili aralığı indir]` ve `[Tüm zinciri indir]`.

### 5.1 İçerik

| Dosya | Ne |
|---|---|
| `BENIOKU.md` | prosedür: uygulama kodumuz olmadan nasıl doğrulanır |
| `muhurler.json` | mühürler: kök, önceki, zincir, algoritma, satır aralığı, tanık referansı |
| `dogrula.py` | bağımsız doğrulayıcı — iki mod: `psycopg` ile doğrudan, ya da **sıfır bağımlılık** ile psql çıktısından (§9.2) |
| `sorgular.sql` | elle hesaplamak isteyen için ham SQL |
| `yaprak-kurali.md` | `sha256-utc-iso-v2`'nin tam tanımı: uzunluk öneki, NULL işareti, UTC zaman biçimi |
| `tanik.txt` | anchor deposundaki commit adresleri |

### 5.2 Pakette ne YOK

⛔ **Audit satırı yoktur.** Satırlar kişisel veri taşır; kök ise kapsadığı şey hakkında hiçbir şey söylemez — bu, kökü yayımlamayı güvenli kılan ve aynı zamanda faydalı kılan özelliktir. Bilirkişi satırları veritabanından kendisi okur; biz ona **neyi nasıl hesaplayacağını** veririz.

### 5.3 Bağlantı noktası

Seçili aralık paketinde, aralığın **ilk mührünün neye bağlandığı** da yazılır (`previous_chain_hash` ve ondan önceki mührün kimliği). Zincir geriye bağlıdır; bir aralığı bağlamsız vermek, zincirin o aralıkta başladığı izlenimi bırakır.

---

## 6. Doğrulama başarısız olursa

1. **Ekran kırmızı:** hangi mühürde kırıldığı ve neyin uyuşmadığı (üç kök yan yana).
2. **Kalıcı bulgu kaydı** — `audit_chain_findings`: hangi mühür, ne uyuşmadı, üç kök değeri, bulan koşu, ilk görülme zamanı. Bir daha koşmadan da görünür.
3. **Bildirim:** `audit.verify` yetkilileri **ve Discord `prod-debug` kanalı.**

### 6.1 Discord kuralının bu bulgu için gevşetilmesi *(Sets, 10.08.2026)*

⛔ Bugüne kadar geçerli kural: *"`debug` ve `prod-debug` uygulamanın işi değildir, Sentry'nin botuna aittir"* — `DiscordChannel` enum'ı üç case taşır, `config/notifications.php` beş webhook'un üçünü okur, ve **iki arch testi bunu kilitler** (`NotificationChannelTest`: "never writes to the channels the outage bot owns" + "reads no debug webhook out of the environment").

Sets bu kuralı **yalnız zincir bulgusu için** gevşetti: orası zaten debug kanalı ve kırılmış bir audit zinciri tam olarak o kanalı açık tutan kişinin görmek istediği şeydir. Kuralın var oluş sebebi — *"bir uygulama satırı beklenen uyarıyı gömer"* — burada tersine döner: gömülen değil, beklenen uyarı budur.

⚠ **Şart: mesajın kimden geldiği net olacak.** Sentry botunun mesajlarıyla karışmamalı, çünkü ikisi farklı şeyler söyler: biri *"kod patladı"*, diğeri *"delil zinciri tutmuyor"*. Discord webhook'unun `username` alanı **`3eegitim-audit-bot`** olarak gönderilir ve mesaj başlığı da bot adını taşır; `3eegitim-outage-bot`'un satırlarından ilk bakışta ayrılır.

Bu iki dosyayı değiştirir ve **arch testleri buna göre güncellenir** (yeni beklenen: dört case / dört anahtar):
- `DiscordChannel` enum'a yeni case — kanal `prod-debug` webhook'una bağlıdır, gönderici kimliği ayrıdır.
- `config/notifications.php` bu dördüncü kanalı okur.

⚠ Gevşetme **bu tek olay içindir**. Başka hiçbir uygulama satırı bu kanala yazmaz; arch testi dördüncü case'i adıyla bekleyerek bunu kilitler.

⛔ **Bulgu kaydı silinmez ve "çözüldü" diye kapatılmaz.** Bir kişi not düşebilir, o kadar. Gerekçe AD-11: kırılmış zincir bulgusunu kapatılabilir yapmak, delili yönetilebilir yapmaktır.

⚠ `VerifyAuditChain` zaten `Log::critical` yazıyor ve **10.08.2026'dan beri o satır Sentry'ye ulaşıyor** (EGT-95): `sentry` log kanalı `critical` seviyede bağlı. Bulgu kaydı bunun yerine geçmez — biri alarmdır, diğeri kayıttır.

---

## 7. Loglama (AD-21)

| Eylem | Nereye | Neden |
|---|---|---|
| Sayfayı açmak | `audit_usage` | mühür özetleri hassas kayıt değil, kişisel veri taşımaz |
| Doğrulama koşturmak | `audit_writes` | bir işlemdir: kim, ne zaman, hangi aralık |
| Delil paketi indirmek | `audit_writes` | dışarı çıkan bir çıktıdır |

⚠ Son ikisinin mühürlenen tabloya yazılması bilinçlidir: **delil aracının kendi kullanımı da delil zincirinin içinde kalır.**

---

## 8. Kabul kriterleri

1. `audit.view_chain` olmayan hesap sayfayı göremez (403); `audit.view_chain` olup `audit.verify` olmayan hesap sayfayı görür ama dört eylem düğmesini göremez.
2. Dört kartın sayıları `audit_stamps`'ten gelir ve tanık/arşiv eksiklerini doğru sayar.
3. Bir mühür seçilince künye, Merkle ağacı ve log yapısı bloğu dolar.
4. Tek mühür doğrulaması senkron çalışır ve üç kökü karşılaştırır.
5. Tam zincir doğrulaması kuyruğa gider; sayfa ilerleme gösterir; bitince sonuç ekrana düşer.
6. Kasten bozulmuş bir satır (test ortamında) doğrulamayı **kırar**, bulgu kaydı açar, bildirim üretir.
7. Delil paketi indirilir, açılır ve `dogrula.py` **bu repodaki hiçbir dosyaya bağımlı olmadan** koşar; canlı veritabanına karşı kökleri yeniden hesaplar ve eşleştirir.
8. Paket hiçbir audit satırı içermez.
9. Sayfayı açmak `audit_usage`'a, doğrulama ve indirme `audit_writes`'a satır yazar.
10. `audit_chain_findings` kaydı silinemez ve kapatılamaz; not eklenebilir.

---

## 9. Cevaplanan üç soru *(Sets, 10.08.2026)*

**1 · Bulgu bildirimi hangi kanaldan?** Discord `prod-debug` — §6.1. Şart: gönderici `3eegitim-audit-bot` adıyla görünür, Sentry botundan ayrılır.

**2 · `dogrula.py` hangi Python sürümünü hedefler?** **3.12** hedeflenir ve orada test edilir.

⚠ Ama hedef sürüm tek başına yetmez, çünkü bilirkişinin makinesinde ne bulunacağı bilinemez — ve bir delil aracının ilk adımı `pip install` olmamalıdır. Bu yüzden script **iki modda** çalışır:

| Mod | Bağımlılık | Ne zaman |
|---|---|---|
| **Doğrudan** | `psycopg` | doğrulayanın veritabanına erişimi ve kurulumu varsa |
| **Dosyadan** | **yok — yalnız stdlib** | `sorgular.sql` psql ile koşulur, çıktısı script'e verilir |

İkinci mod delil açısından **daha güçlüdür**: veriyi veritabanından çıkaran şey bizim kodumuz değil, doğrulayanın kendi `psql`'idir. `BENIOKU.md` iki modu da adım adım yazar ve hedef sürümü açıkça söyler.

⚠ Yalnız stdlib kullanıldığı sürece 3.9+ uyumluluğu bedavaya gelir; gereksiz yere yeni sözdizimi kullanılmaz.

**3 · Paket imzalanacak mı?** **Hayır — ve KamuSM bu projede kullanılmıyor** (Sets, 10.08.2026). Dış tanık yerine **kendi yapımız** var: mühürler `Sets/3eegitim-anchor` deposunda yayımlanır, depo public, force-push ve silme engelli. Paketin doğruluğu bir imzayla değil, `tanik.txt`'deki commit'lerle karşılaştırılarak sınanır — ve bu daha iyi bir iddiadır: imza "biz öyle diyoruz" der, tanık deposu "biz o gün öyle demiştik ve değiştiremeyiz" der.

⚠ **Yan bulgu, soruldu ve karara bağlandı:** `config/audit.php` ve `.env`/`.env.example` hâlâ `KAMUSM_TSA_URL`, `KAMUSM_TSA_USER`, `KAMUSM_TSA_PASSWORD` taşıyor. Kullanılmayan bir ayar, bir gün birinin doldurup çalıştığını sanacağı bir ayardır — `usage_retention_days` tam olarak bu sebeple silinmişti (07.08.2026). **Sets, 10.08.2026: dursun, şimdilik bir zararı yok.** Bu ekranın işi değil; temizlenecekse kendi kalemiyle temizlenir.
