# Review — Omurga Chunk 3 (Audit) Implementation — 2026-07-26 — r3

> **Review type:** impl re-review (heyet 135'in 15 bulgusunun kapanış doğrulaması + değişen alanların taze taraması)
> **Previous review:** `review-omurga-chunk3-impl-2026-07-26-r2.md` (❌ REJECTED — "mühür üretimi sağlam, mührün bekçiliği değil")
> **Upstream document(s) read:** `docs/plans/omurga-plan_chunk3.md` (v2.4.5) · `docs/plans/omurga-plan.md` (2.4.5 girdisi) · `docs/plans/omurga-plan_chunk2.md` §2.5.1 (run(Closure) tablosu), §2.6 G-2/G-3, §2.6.1 · `docs/specs/omurga-spec.md` **v1.10** (§7.1 + revizyon girdisi) · `docs/arch/mimari-kararlar.md` AD-06 (artık risk paragrafı) · chunk6 §6.9-16 · chunk8 §8.9-16 · `docs/issues.md` · `docs/claude/heyet.md` (KamuSM temizliği)
> **Stage locks in effect:** Spec v1.10 + Plan v2.4.5 locked
> **Reviewed artifact:** branch `feature/EGT-32-audit`, HEAD `e49ef45`; çalışma ağacı temiz
> **Bağımsız doğrulama (bu review'da koşuldu):** `composer test` → **91 + 147 = 238 yeşil** · `phpstan` **0** · `pint --test` temiz · TeamCity **#714 SUCCESS** ve **#715 SUCCESS (238 test — kolon-pin probu dahil, HEAD kapsanıyor)**

---

## Bölüm 1 — Heyet 135 bulgularının kapanış doğrulaması

Tamamı koddan/dokümandan yeniden okundu; kanıtlar gerçek execution'dan (testler bu review'da koşuldu).

| 135 bulgusu | Durum | Bağımsız kanıt |
|---|---|---|
| **F-1** yaprak yalnız `(id, created_at, tenant_id)` kapsıyordu | ✓ kapalı | **Yaprak v2:** `app.audit_leaf_line(r audit_writes)` 16, `(r audit_access)` 14 part — iki tablonun **tüm kolonlarıyla** birebir eşleştiğini `information_schema`'ya karşı kendim saydım. Kural `sha256-utc-iso-v2`; kriter 28 testi (`catches a rewritten CONTENT column…`) `new_values`/`user_id`/`event` tahrifatının **VerifyAuditChain tarafından** raporlandığını gösteriyor — r2'deki kusurun tam tersine, artık iş de görüyor, yalnız test değil |
| **F-2** doğrulama kökleri karşılaştırmıyordu; `p_to_id` yoktu | ✓ kapalı | `audit_batch_merkle_root(text, bigint, bigint DEFAULT NULL)`; `verifyAgainstRows` kökü **ve** satır sayısını mühürdekiyle karşılaştırıyor; kriter 30 testi iki mühürlü zincirde **ilk** partinin içindeki tahrifatı yakalıyor (üst sınır olmadan yapısal olarak imkânsızdı). Yan kazanç: r2'nin kuadratik-tarama notu da kapandı — tarama artık parti sınırlı, günlük toplam iş doğrusal |
| **F-2 (kural tüketimi)** `hash_algorithm` okunmuyordu | ✓ kapalı | `verifyAgainstRows` ilk iş kural adını okuyor; tanımadığı kuralı **"cannot recompute"** olarak raporluyor (sessiz geçmek de yanlış kuralla karşılaştırmak da reddedilmiş). Kriter 31 testi trigger'ı kapatıp kural adını değiştirerek bunu sınıyor |
| **F-3** `published()` fetch etmiyordu | ✓ kapalı | Metod her çağrıda `syncWorkspace` (fetch + `reset --hard`); `alreadyPublished` çift fetch'i önlemek için ayrılan `readSeals`'ı kullanıyor (publish zaten sync'lemiş durumda — gerekçe yorumda). Kriter 32 testi **gerçek bare repo fixture'ı** üzerinde workspace dosyasını elle bozup fetch'in gerçeği geri getirdiğini gösteriyor |
| **F-4** `compareWithWitness` iki bacağı hiç koşmamıştı | ✓ kapalı | `RecordingWitness` (tutan double) + `plant()`/`forget()`; iki test iki yönü de gerçek `VerifyAuditChain` üzerinde koşturuyor ("published seal is missing locally" / "local seal is missing from the witness") |
| **F-5** `witnessed_at`'e bizim saatimiz yazılıyordu | ✓ kapalı | `WitnessReceipt::recordedAt` nullable; git tanığı iki dönüş noktasında da **null**; şema yorumu düzeltildi; `PublishAuditRoots` taraması `witness_reference`'a anahtarlandı (null zamanın "yayımlanmamış" okunup her gün yeniden yayımlanması engellendi — testli); `alreadyPublished` yolunda HEAD yerine hash'i getiren commit dönüyor (aşağıda soru 5) |
| **F-6** G-2 digest kategorisi niteliksizdi | ✓ kapalı | G-2'ye iki koşul (küme özeti + sınırlar sistem kaydından); tek satırlık parti **AD-06'ya kabul edilmiş artık risk** olarak gerekçesiyle yazıldı, G-2 atıf yapıyor (aşağıda soru 3) |
| **F-7** §3.9 eski CA satırı | ✓ kapalı | Satır düzeltildi, düzeltme notuyla birlikte |
| **F-8** spec sürüm damgası | ✓ kapalı | Spec **v1.10** + tam revizyon girdisi (değişen iki madde + gerekçe); plan pin'i v1.10'a çekildi |
| **F-9** chunk2 §2.5 / issues.md senkronu | ✓ kapalı | §2.5.1'e `run(Closure)` iki-durumlu kesin tablo (gerekçesiyle: ortam kapsamına dönüş sessiz genişletme olurdu); issues.md tanık kalemi güncel |
| **F-10** kriter 5 kanıtı incelmişti | ✓ kapalı | "writes no seal at all…" testi audit satırının ayakta kaldığını admin bağlantısından assert ediyor (bekçi gerekçesi yorumda doğru) |
| **Açık soru 3** orphan feed'ler | ✓ tutanağa geçti | `docs/issues.md` "Bilinçli kabul" bölümü + kriter 34 + chunk6 §6.9-16 + chunk8 §8.9-16 — kabul **erteleme** olarak kurgulanmış, kapanışı iki somut kritere bağlı |

**15/15 kapalı** (F-1…F-10 + r2'nin alt kalemleri). Yeni kriterler 28–34'ün yedisi de gerçek testlerle karşılanıyor; kriter 27 artık fiilen kapalı.

## Bölüm 2 — Sorulan altı noktanın doğrulaması

**1. Yaprak v2 tam mı? — Evet, bir dokümantasyon şerhiyle.**
- **Uzunluk öneki belirsizliği kapatıyor:** `octet_length || ':' || değer || ';'` klasik netstring çerçevelemesi — bayt akışı soldan sağa deterministik ayrıştırılır (rakamlar → `:` → tam N bayt → `;`), `NULL` işareti `-;` rakamla başlamadığı için hiçbir uzunluk önekiyle karışamaz, boş dize `0:;` olarak ayrık. Sabit alan sırası ve sayısıyla birlikte kodlama **injektif**. Ayrıca yaprak/ebeveyn alan ayrışması korunuyor (ebeveyn girdisi saf 128-hex; yaprak `;`/`:` içerir).
- **`updated_at` kapsanması doğru:** append-only tabloda insert anında `created_at`'e eşit (audit_access'te NULL) ve değişemez; kapsanmasaydı superuser bump'ı görünmez kalırdı. NULL yolu `audit_leaf_time(NULL)→NULL→'-;'` ile tanımlı.
- **`jsonb::text` şerhi:** çıktı oturumdan bağımsız ve PG içinde deterministik (anahtar sıralaması, tekilleştirme, boşluk normalizasyonu jsonb'nin kanonik biçimi), yani zincir kendi içinde sağlam. Ancak bu biçim PostgreSQL'in **sözleşmesel garantisi değildir**: PG dışı bir doğrulayıcının jsonb serileştirmesini birebir kopyalaması gerekir ve bir PG major upgrade'i biçimi değiştirirse v2 kökleri toplu "mismatch" üretir. Bu **sessiz değil gürültülü** kırılır (günlük doğrulama bağırır → kural sürümü zorlanır) — yine de kural tanımının (anchor README) jsonb biçimini açıkça yazması gerekir; repo dışında olduğu için doğrulayamadım → açık soru 1.

**2. Kolon-pin testi kaçak bırakmıyor.** Regex iki yönde de kelime sınırlı: `_` bir `\w` karakteri olduğundan `/\br\.id\b/` `r.id_extra`'yı yakalamaz ve `id_extra` araması `r.id`'yle tatmin olmaz — implementer'ın kendi yakaladığı ön-ek kusuru gerçekten kapanmış. Kasıtlı ihlal probu gerçek (canlı `ALTER TABLE ADD COLUMN` + `finally` ile temizlik + temizlik sonrası yeniden yeşil assert'i). Tek teorik kaçak: fonksiyon gövdesine `r.yeni_kolon` içeren bir **yorum** yazmak pin'i hash'lemeden tatmin eder — pin'in hedefi kazara daralma olduğundan (kasıtlı kaçırma S-9/review konusu) bunu bulgu değil not sayıyorum.

**3. Tek satırlık parti kabulü doğru yerde ve yeterince dar.** AD-06'da gerekçesiyle (CT karşılaştırması, tuzlamanın iki yönlü imkânsızlığı), G-2'den atıfla, azaltıcı etken ve son savunma (AD-16 maskeleme) doğru sırayla yazılmış. Bir gözlem: tek satırlık partide bile saldırganın mikro-saniyeli `created_at`'i tahmin etmesi gerekir, ama bu güvence **sayılmamalı** — 10⁸ mertebesinde offline sha256 önemsizdir; AD-06'nın maskeye dayanan son cümlesi doğru vurgudur ve öyle kalmalı.

**4. `p_to_id` ↔ G-2(b) — gerçek bir sözleşme gerilimi var, kod değil doküman düzeyinde** (aşağıda F-r3-1). Üretimdeki iki çağıran da sınırları sistem kaydından alıyor (SealAuditBatch: son mührün `to_id`'si; VerifyAuditChain: mührün kendi sınırları) ✓. Yeni bir çağıran `'audit_writes'` dizgesini taşımak zorunda olduğundan chunk9 S-12 taramasına takılır — fiilî zorlama bu. Ama G-2(b) bu zorlama adresini adlandırmıyor ve fonksiyon imzası kuralı kendisi zorlayamıyor; sözleşme bunun bilinçli olduğunu söylemeli.

**5. `commitCarrying` doğru — çift geçiş durumu dahil.** `-S` yalnız dizge sayısının değiştiği commit'leri listeler; aynı hash dosyada **meşru olarak iki kez** geçer (N. mührün `chain_hash`'i, N+1. mührün `previous_chain_hash`'i olarak) → `-S` iki commit döndürür ve `--reverse` **ilkini** (hash'i getireni) seçer — tam istenen davranış. Yol `roots/<tablo>` ile sınırlı, diğer tablonun dosyası karışamaz. HEAD fallback'i fiilen ölü kod (çağrı öncesi `alreadyPublished` + `reset --hard` dizge varlığını commit'li durumda garanti eder) — güvenlik ağı olarak zararsız.

**6. `RecordingWitness` gerçek kurguyu sınıyor.** Assert'ler double'ın değil `VerifyAuditChain`'in çıktısı üzerinde; tuttuğu payload üretimle aynı biçimde (SealAuditBatch'in yayımladığı dizinin kendisi); `plant`/`forget` iki yönü bağımsız üretiyor. Git katmanının tazeliği ayrı ve **gerçek** bare-repo testiyle kapalı — iki yarım birlikte kompozisyonu makul kapsıyor. Bir inceliği de doğru yapmış: "witness'ta hiçbir şey yok" durumu bulgu değil "tanık yok" okunduğundan, `forget` testi tanıkta başka bir kayıt bırakıyor.

## ✅ What works

- Bu turun ana işi — **bekçilik katmanı** — artık iddiasıyla eşit: içerik tahrifatı kök düzeyinde görünür (F-1), zamanlanmış iş onu gerçekten arar (F-2), tanık kopyası taze okunur (F-3) ve iki yönlü karşılaştırmanın ikisi de test edilmiş durumda (F-4). r2'deki "üretim sağlam, bekçilik değil" tespiti bu haliyle geçersizleşti.
- **Kolon-pin + kasıtlı ihlal probu**, F-1 sınıfı kusurun migration kılığında geri dönüşünü yapısal olarak kapatıyor — heyetin istemediği ama bulgunun mantığından türeyen doğru ek önlem.
- `witnessed_at`'in null bırakılması ve "yayımlanmışlık = referans" ayrımı, delil alanında **iddia disiplinin** doğru uygulaması; mahkemeye gidecek kolonda uygulama saati yok.
- Upstream süreci yine temiz: spec v1.10 damga+girdi birlikte, plan 2.4.5 tam bulgu tablosuyla, orphan kabulü erteleme olarak üç yerde bağlanmış.

## ⚠️ Findings (hepsi minör — yeni tur gerektirmez)

**F-r3-1 — G-2(b)'nin zorlama adresi yazılı değil.** Kural "çağıran kümenin sınırlarını serbestçe seçemez" der; `audit_batch_merkle_root(text, from, to)` imzası bunu zorlamaz ve zorlayamaz — fiilî zorlama, çağıranın taşımak zorunda olduğu `'audit_writes'` dizgesinin S-12 allowlist taraması + S-9/G-5 review kapısıdır. Bu bilinçli durum sözleşmede adlandırılmadıkça, ileride birisi imzaya bakıp "kural ihlal ediliyor" ya da tersine "sınır kontrolü fonksiyonda" sanabilir. Bir cümlelik G-2(b) eki yeterli.

**F-r3-2 — "v1 halkalar v1 olarak doğrulanır" iddiasının kod karşılığı yok.** Migration yorumu böyle diyor; oysa v1 fonksiyonları düşürüldü ve `VerifyAuditChain` yalnız güncel kuralı hesaplayabiliyor — eski kurallı mühür süresiz "cannot recompute" bulgusu üretir. Bugün doğru davranış (hiçbir ortamda v1 mührü yok; prod deploy yok) ve raporlamak sessiz geçmekten doğru — ama (a) yorum, sistemin yapamadığı bir şeyi vaat ediyor; (b) bir sonraki kural değişiminde eski halkalar **sonsuza dek günlük critical log** üretecek. Çok-kurallı doğrulama ihtiyacı ilk gerçek kural değişiminde doğacak; şimdiden `docs/issues.md`'ye kalem olması yeterli.

**F-r3-3 — Yaprak kuralının jsonb bileşeni repo içinde tanımsız.** Bağımsız doğrulanabilirlik `old_values::text`/`new_values::text`/`context::text`'in PostgreSQL jsonb serileştirmesine bağlı (anahtar sıralaması, boşluk, sayı biçimi); bu biçim PG'nin sözleşmesel garantisi değil ve kuralın yazılı tanımı yalnız anchor README'de (repo dışı, bu review'da doğrulanamadı). PG major upgrade'i biçimi değiştirirse kırılma gürültülü olur (doğrulama bağırır → kural sürümlenir), sessiz değil — yine de kural tanımının jsonb biçimini birebir yazması ve upgrade'in kural-sürümü tetikleyebileceğinin not edilmesi gerekir.

**Notlar (bulgu değil):** pin regex'i fonksiyon gövdesindeki yorumla teorik olarak atlatılabilir (kasıtlı kaçırma sınıfı, S-9/review alanı) · iki `SealAuditBatch` işi aynı workspace klonunu paylaşıyor; eşzamanlı koşumda git kilidi geçici hataya düşebilir — güvenli yön (mühür oluşmaz, sonraki tur telafi) · `epoch_date` gün-dönümü kenar durumu (r2 notu) geçerliliğini koruyor, chain_hash karşılaştırmasını etkilemiyor.

## ❓ Open Questions

1. **Anchor README** v2 kuralını (uzunluk öneki, kolon sıraları, jsonb kanonik biçimi, `audit_leaf_time` kalıbı, katlama kuralı) birebir içeriyor mu? Kural v1→v2 değişti; README güncellenmediyse dışarıdan doğrulayıcı v2 köklerini yeniden üretemez. Repo dışı olduğu için bu review doğrulayamadı — kapanış kanıtı bir README linki/kopyası olabilir.
2. F-r3-2'nin kalemi: çok-kurallı doğrulama (veya "eski kural halkaları için kabul edilen davranış") `docs/issues.md`'ye yazılacak mı?

## Verdict: ✅ APPROVED

15/15 kapanış bağımsız doğrulandı; yeni kriterler 28–34 gerçek testlerle karşılanıyor; CI #715 (238 test, HEAD dahil) + lokal 238 + PHPStan 0 + Pint temiz bu review'da yeniden üretildi. Kalan üç bulgu sözleşme/doküman cilası düzeyinde ve heyet 133 emsalindeki gibi yeni tur gerektirmiyor — kapanışları bir sonraki doküman dokunuşuyla yapılabilir. r2'nin verdictindeki cümlenin bugünkü karşılığı: **mühür üretimi de bekçiliği de ayakta.** Son karar Sets'indir; merge öncesi zorunlu adımlar zaten yerine gelmiş durumda (full CI yeşil).
