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

> **Review type:** impl-review
> **Previous review:** N/A (chunk3'ün ilk impl review'u)
> **Upstream document(s) read:** `docs/plans/omurga-plan_chunk3.md` (v2.4.3 zinciri) · `docs/plans/omurga-plan.md` (index) · `docs/specs/omurga-spec.md` v1.9.1 §7, §8.3, §8.5 · `docs/plans/omurga-plan_chunk2.md` §2.2, §2.5, §2.6, §2.8
> **Other cross-referenced documents:** `CLAUDE.md` (17 kırmızı çizgi, dil kuralı, onay kuralları) · `docs/arch/mimari-kararlar.md` AD-06/AD-16/AD-21 (+AD-08, AD-09) · `docs/arch/kod-yerlesimi.md` · `config/tenancy.php` · `vendor/owen-it/laravel-auditing` kaynak doğrulaması (`Auditable.php`, `Auditor.php`, `AuditingServiceProvider.php`, paket config'i)
> **Stage locks in effect:** Spec v1.9.1 + Plan v2.4.3 locked
> **Reviewed artifact:** branch `feature/EGT-32-audit` — commit `ed65adc` **+ çalışma ağacındaki commit'lenmemiş revizyon** (damga → Merkle mühür zinciri; aşağıda F-1/F-2). TeamCity #708 SUCCESS bağımsız doğrulandı (`teamcity.sh status 708`) — ancak #708 yalnız `ed65adc`'yi kapsar; çalışma ağacının şu anki hali **derlenmez** (F-2).

---

## Ön not — değerlendirilen durum

Review talebi `ed65adc` içindi. Depoda review sırasında commit'lenmemiş bir revizyon duruyor: KamuSM adaptörü (`TimestampProvider`, `NullTimestampProvider`, `TimestampToken`) **silinmiş**, `StampAuditBatch` → `SealAuditBatch` olarak yeniden yazılmış, `audit_stamps` şeması Merkle kökü + zincir hash'i + "external witness" alanlarına dönüştürülmüş. Heyet kuralı gereği onay üretilmiş artifact'e dayanır; artifact bir bütün olarak bugünkü ağaç halidir ve matrix ile bulgular bu hale göredir. `ed65adc`'de yeşil olup bu revizyonla bozulan satırlar ayrıca işaretlidir.

---

## Upstream Coverage Matrix

### Plan bölümleri

| Upstream § / madde | Durum | Kanıt |
|---|---|---|
| §3.1 üç log sınıfı, üç ayrı tablo + `audit_stamps` | ✓ | `2026_07_26_000100_create_audit_tables.php:62-171,196+`; `config/audit.php:23-28` |
| §3.1 iş akışı ekranları audit'ten beslenmez | ✓ (bu chunk'ta ekran yok; zorlama chunk9 S-13) | ekran/route üretilmedi; `AuditUsage` docblock kuralı taşıyor `app/Models/AuditUsage.php:11-16` |
| §3.2 append-only trigger, `RAISE EXCEPTION`, RULE değil; `audit_usage`'a kurulmaz | ✓ | migration `denyMutationOnEvidence()`; `AuditAppendOnlyTest.php` (UPDATE/DELETE `42501`, sessiz-yutma probu, usage'da trigger yok) |
| §3.3 audit işlemle aynı transaction, kuyruk yok, hata yutulmaz | ✓ | `AuditWriter.php` (catch yok); `AuditWriteGuaranteeTest` "takes the operation down…" gerçek başarısızlıkla; paket `audit.queue.enable` default `false` (vendor doğrulandı) |
| §3.3 damga/WORM async, dış servis akışı durdurmaz | ✓ (`ed65adc`) / ⚠ (ağaç: sealing artık dış servissiz, witness tanımsız) | `AuditWriteGuaranteeTest` "keeps the business record when only the stamping fails"; F-1 |
| §3.4 owen-it v14, kendi tablo/model/driver | ✓ | `config/audit.php:55-56`; `AuditDriver.php`; `AuditWrite::getTable()`; paket `mergeConfigFrom` doğrulandı |
| §3.4 bu chunk'ta üretilen olaylar: kimlik doğrulama | ✓ | `RecordAuthenticationEvent.php`; `AuditServiceProvider.php:63-69`; `AuthenticationAuditTest.php` (6 test) |
| §3.4 zorlama: `BelongsToTenant` ⇒ `Auditable` arch testi | ✓ | `tests/Arch/AuditTest.php:42-59` (kasıtlı ihlal probuyla) |
| §3.5 erişim logu: elle işaretleme, `sensitive_fields` config, sert garanti, N4 daraltma | ✓ | `AccessLogger.php`; `config/audit.php:106-109`; `AuditWriteGuaranteeTest` kriter 8 ve 2c testleri |
| §3.5 kullanım logu: `LogUsage` middleware **yalnız liste/detay rotalarında** | ⚠ | `app/Http/Middleware/LogUsage.php:40-53` — her adlandırılmış GET rotası loglanıyor; rota sınıfı filtresi yok → **F-7** |
| §3.5 M1 yazım bağlamı tablosu (müşteri `forWrite`, ofis NULL) | ✓ | `PersistsAuditRows.php:40-63`; `CurrentActor::tenantId()/ambientScope()`; "actually writes usage rows in both identity domains" testi |
| §3.5 kullanım logu kimseye satır satır gösterilmez | ✓ | rota/ekran yok; kriter 18 arch taraması `tests/Arch/AuditTest.php:61-82` |
| §3.6 tablo tenant durumu + NULL disiplini + politika SQL'i | ✓ | `AuditPolicy.php:62-99` (plan §3.6 SQL'iyle birebir); `AuditSchemaTest`; `AuditTenantDisciplineTest` |
| §3.6 N-1 müşteri olayı NULL yazamaz (exception) | ✓ | `PersistsAuditRows.php:71-80`; "refuses a customer domain event without a tenant" |
| §3.6 N-2 (policy katmanı, chunk6) | ✓ not edildi | `docs/issues.md` kalemi; kriter 11b satırına bakınız (❌) |
| §3.6 N-4 aktör ayrımı, üç değer, `participant` yok | ✓ | `ActorType.php`; migration `actor_type` kolonları; `tests/Arch/AuditTest.php:84-90`; `AuditActorSeparationTest` |
| §3.6 ofis feed'i `office_user`+`system`, imzada aktör parametresi yok | ✓ | `OfficeAuditFeed.php:41-44`; reflection testi |
| §3.6 kimlik doğrulama olayları feed'de, türetilmiş metrik yok (L-1b) | ✓ | `AuditActorSeparationTest` "keeps authentication events…", "returns events and never a derived metric" |
| §3.6.1 tek giriş noktası `BackofficeAuditFeed`, izin → bayrak → okuma → erişim logu | ✓ (mekanizma) / bulgu F-6 (erişim logu satırı taşabiliyor) | `BackofficeAuditFeed.php:80-102,117-127` |
| §3.6.1 B-1 bayrak yalnız `SET LOCAL`, transaction içinde | ✓ | `AuditReadScope.php:52-58,92-100`; "opens only on the exact value" |
| §3.6.1 B-2 bayrağı kuran tek yer feed, izin kontrolünden sonra | ✓ (uygulamada; tek-yer zorlaması chunk9 S-11) | `BackofficeAuditFeed.php:80-82` |
| §3.6.1 B-3 bayrak yalnız iki evidence tablosunda | ✓ (politika yüzeyi) / ⚠ (kasıtlı ihlal probu yok) | `AuditPolicy` usage varyantı bayraksız; `AuditSchemaTest:88-103`; **F-10** |
| §3.6.1 B-4 bu yolda BYPASSRLS / SECURITY DEFINER yok | ✓ (yol için) | "keeps no bypassing role or function on this path"; digest/rollup fonksiyonları ayrı mesele → **F-5** |
| §3.6.1 B-5/B-5a önceki değer + `finally` geri yükleme; yalnız `'on'` açar | ✓ | `AuditReadScope.php:60-90`; kriter 17b/17b-2/17b-3 testleri |
| §3.6.1 B-6 bekçi muafiyeti bileşene | ✓ | `TenantContextGuard.php:62-99`; "exempts the reading component, not the table" |
| §3.6.1 B-7 `OfficeAuditFeed` ortam kapsamı, muafiyetsiz, bayraksız | ⚠ | sınıf `TenantContext::run()`'ı **kendisi açmıyor**, bağlamı testler dışarıdan kuruyor → **F-11** |
| §3.6.1 B-8 feed'in erişim logu NULL tenant, bayrak kapandıktan sonra | ✓ | `BackofficeAuditFeed.php:102,117-127`; "logs the read itself, with the flag already closed" |
| §3.7 maskeleme yazım anında, `Masker`, `masked_fields` config | ✓ şekiller / ⚠ liste | `Masker.php`; `MaskingTest`; **ama `masked_fields` e-posta içermiyor → F-8** |
| §3.8 hypertable (30g/30g/7g chunk) | ✓ | `..._000200:40-67`; `AuditSchemaTest` hypertable testi |
| §3.8 **compression (90g/7g)** | ❌ | uygulanmadı — `..._000200:22-38` sapma bloğu; plan metni değişmedi → **F-3** |
| §3.8 retention yalnız `audit_usage`, config'ten | ✓ | `..._000200:79-85`; `AuditSchemaTest` "drops old rows from the usage log only" |
| §3.8 bileşik PK `(id, created_at)` | ✓ | migration `:98,132,166` |
| §3.8 damga: saatlik batch hash + **KamuSM RFC 3161** + `audit_stamps` | ❌ (çalışma ağacı) | adaptör silindi, RFC 3161 akışı Merkle zinciri + "witness" ile değiştirildi → **F-1**; `ed65adc`'de ✓ idi (`StampAuditBatch`, `NullTimestampProvider`, kriter 5/14 testleri) |
| §3.8 `app/Integrations/KamuSM/` arayüz + `NullTimestampProvider`, gerçek sağlayıcı issues.md | ❌ (çalışma ağacı: dizin boşaltıldı) | `git status`: üç dosya silinmiş; `docs/issues.md` kalemi hâlâ arayüzü var sayıyor |
| §3.8 WORM: `ArchiveAuditBatch` günlük, Object Lock | ⚠ | job var ve schedule'da (`AuditServiceProvider:71-99`); ancak çalışma ağacında `stamped_at`/`digest`/`token` kolonları kalktığı için job artık şemayla uyumsuz → F-2 |
| §3.9 `analytics_usage_daily` **continuous aggregate** | ❌ | zamanlanmış rollup + `SECURITY DEFINER` yazıcı fonksiyonla değiştirildi — `..._000200:96-186`; plan metni değişmedi → **F-4** |
| §3.9 `user_id` aggregate'te yok; `person_count` tam distinct | ✓ | `..._000200:110-135`; `AuditSchemaTest` "keeps user_id out…", "counts distinct people exactly…" |

### §3.10 kabul kriterleri

| Kriter | Durum | Kanıt |
|---|---|---|
| 1 dört tablo | ✓ | `AuditSchemaTest` "has the four audit tables" |
| 2 UPDATE/DELETE exception + `USING (true)` politikaları | ✓ | `AuditAppendOnlyTest` (SQL denemesi, sessiz-yutma probu); `AuditSchemaTest` politika testi |
| 2b bağlamsız NULL INSERT ✓ / çok-tenant tenant'lı INSERT ✗ / çapraz INSERT ✗ | ✓ | `AuditTenantDisciplineTest` + `AuditWriteGuaranteeTest` "refuses a tenant row written from a multi tenant scope" |
| 2c geniş kapsamda görüntüleme engellenmiyor, satır daraltılarak yazılıyor | ✓ | `AuditWriteGuaranteeTest` "lets a wide office scope view a sensitive field" (`tenant_id=7` assert) |
| 3 usage retention var, trigger yok | ✓ | `AuditSchemaTest` retention testi; `AuditAppendOnlyTest` trigger sayımı |
| 4 audit engellenince iş kaydı yok | ✓ | gerçek başarısızlıkla (`audit_writes_missing` yönlendirmesi + `AuditProbeModel`) |
| 5 damga engellenince iş+audit duruyor, damga kuyrukta | ✓ (`ed65adc`) / ❌ (çalışma ağacı: test silinen `TimestampProvider`'ı import ediyor, koşamaz) | `AuditWriteGuaranteeTest:1-20` importları ↔ `git status` silmeleri → F-2 |
| 6 `BelongsToTenant` ⇒ `Auditable` arch testi, kasıtlı ihlalle | ✓ | `tests/Arch/AuditTest.php:42-59` |
| 7 hassas alan güncellemesi sonrası ham değer yok (string arama) | ⚠ | maskeleme yalnız `AuditWriter` yolunda sınanıyor (`AuditTenantDisciplineTest` "masks sensitive values…"); paket **driver** yolunda hassas alanlı model güncellemesi + string arama testi yok → **F-9** |
| 8 erişim logu yazılamazsa gösterim yok | ✓ | "does not show a sensitive value when its access row cannot be stored" |
| 9 usage yazılamazsa sayfa engellenmez | ✓ | "keeps rendering when the usage row cannot be written" |
| 9b usage gerçekten yazılıyor (iki alan), bekçiye takılmıyor | ✓ | "actually writes usage rows in both identity domains" |
| 10 N satır = tek kullanım kaydı | ✓ | "records one usage row for a list of many records" (result_count 200) |
| 11 nullable + müşteri alanında NULL exception | ✓ | şema testi + N-1 testi |
| 11b müşteri kimliği NULL satırları göremiyor | ❌ | chunk3'te sınanamıyor (customer_users chunk5, izin chunk6); `docs/issues.md`'ye yazılmış; kriterin taşınması **plan değişikliği** → **F-13** |
| 11c ofis girişi NULL ile audit'leniyor | ✓ | `AuthenticationAuditTest` "records an office login with no tenant" |
| 12 üç hypertable; retention yalnız usage | ✓ | `AuditSchemaTest` |
| 13 aggregate'te `user_id` yok | ✓ | `AuditSchemaTest` şema sorgusu |
| 14 KamuSM adaptörü arayüz + null impl; gerçek sağlayıcı issues.md | ✓ (`ed65adc`) / ❌ (çalışma ağacı: adaptör silindi) | `git status`; **F-1** |
| 15 `actor_type` üç tabloda, üç değer, feed ayrımı | ✓ | şema testi + `ActorType::values()` arch pini + N-4 testleri |
| 15b giriş/çıkış feed'de; türetilmiş alan yok | ✓ | `AuditActorSeparationTest` |
| 16 imza aktör parametresi almıyor (reflection) | ✓ | "takes no actor type argument" |
| 17 tek sınıf; izinsiz red; okuma erişim logu üretiyor | ✓ | `AuditCrossTenantReadTest` üç testi; **filtreli çağrıda F-6 taşma riski** |
| 17b `SET LOCAL` + COMMIT sonrası düşme + B-5a sözleşme | ✓ | "opens only on the exact value" + RLS öncesi/sonrası testler |
| 17b-2 transaction içi miras + exception yolu | ✓ | "closes the flag when the scope ends…", "…even when the scope throws" |
| 17b-3 iç içe scope geri yükleme | ✓ | "restores the enclosing scope rather than closing the flag outright" |
| 17c bayrak yalnız iki tabloda + **kasıtlı ihlal probu** | ⚠ | davranış testleri ✓ ("does not reach the usage log / ordinary tenant tables"); **probu yok ve yüzey testi `whereIn` ile üç tabloya sınırlı — başka tabloya taşınan bayrağı hiçbir test yakalamaz** → **F-10** |
| 17d bu yolda BYPASSRLS/SECURITY DEFINER yok | ✓ (yol) | "keeps no bypassing role or function on this path" |
| 17e muafiyet bileşene, tabloya değil | ✓ | "exempts the reading component, not the table" |
| 17f `OfficeAuditFeed` ortam kapsamı, muafiyetsiz, bayraksız | ⚠ | bağlamı sınıf değil çağıran kuruyor → **F-11** |
| 17g erişim logu NULL + bayrak kapandıktan sonra | ✓ | "logs the read itself, with the flag already closed" |
| 18 usage listeleme rotası yok; `audit_activity` adı geçmiyor | ✓ | arch taraması `tests/Arch/AuditTest.php:61-82` (app/config/database/tests kapsamı `scannedPhpFiles` + `config_path`) |
| 19 S-12 taraması | ❌ kapsam dışı | kriterin zorlama evi plan metninde chunk9 S-12; bu review'da bulgu değil (chunk-scope kuralı) |
| 20 S-13 taraması | ❌ kapsam dışı | aynı — chunk9 S-13 |

### Zorunlu üç tarama

1. **Red-line (17 madde):** tenant leak yok (RLS+FORCE+testler); `SET LOCAL` dışı `SET` yok (`AuditReadScope` `SET LOCAL`, arch S-4 deseni chunk2'den); raw `DB::` bypass yok (testlerdeki raw denemeler kasıtlı ihlal probu); elle DDL yok; `env()` config dışı **yok** (grep temiz); hardcoded credential yok; kritik kayıt hard delete yok; audit kuyruğa atılmıyor; kişi bazlı izleme üretilmiyor (`user_id` aggregate dışı, feed türetilmiş metrik döndürmüyor); AI attribution iki commit'te de **yok**. **İstisna:** çalışma ağacındaki mühür revizyonu AD-06'nın açık kararıyla ("Blockchain/zincir yapısı YOK", KamuSM RFC 3161) çelişiyor → F-1.
2. **Wire:** 24 yeni sembolden 22'sinin production caller'ı var (reverse-grep tablosu çıkarıldı: middleware stack `bootstrap/app.php:40`, provider `bootstrap/providers.php`, schedule, config, migration, listener kaydı). `OfficeAuditFeed` ve `BackofficeAuditFeed` **orphan (0 caller)** — ancak plan tüketicileri chunk6/chunk8'e koyuyor ve "omurga iş ekranı üretmez" diyor; bu, kilitli planın kendi sıralama kararıdır, bulgu açılmadı. Çalışma ağacında ek olarak `SealAuditBatch` **hiçbir yerden çağrılmıyor** (provider hâlâ `StampAuditBatch` schedule ediyor) ve bahsedilen `PublishAuditRoots` **mevcut değil** → F-2.
3. **Dil:** kod tarafı tamamen İngilizce (tablo/kolon/sınıf/test adları, yorumlar); kullanıcıya dönük metin üretilmedi. Not (bloker değil): `AuditWriteGuaranteeTest`'te fixture rota adı `'ofis.teklifler.index'` — gerçek rota tanımı değil ama Türkçe rota adını normalleştiriyor; gelecekte gerçek rota adları İngilizce olmak zorunda.

---

## ✅ What works

- **Üç garanti sınıfı gerçekten üç ayrı garanti olarak sınanmış.** Kriter 4 testi elle exception değil, gerçek yazım başarısızlığıyla kanıtlıyor; sert/gevşek garanti ayrımı ve "gevşek ≠ hiç yazılmıyor" (9b) ayrı ayrı test edilmiş. Bu, bu chunk'ın en kolay sahte-yeşil kalacak yeriydi ve kalmamış.
- **`console => true` yakalaması gerçek bir üretim boşluğu kapatmış.** owen-it'in `console => false` varsayılanı kuyruk/schedule/artisan kaynaklı model değişikliklerini audit'siz bırakırdı; AD-21'in "eklemeyi unuttuk mümkün olmamalı" kuralının tam ihlaliydi. Paket kaynağından doğrulandı (`Auditable.php:553-559`).
- **B-5 bayrak yaşam döngüsü sağlam.** Transaction içi miras, exception yolu ve iç içe geri yükleme üçü de gerçek RLS testleriyle kapalı (17b-2/17b-3 doğru şeyi sınıyor: sonuç kümesi üzerinden, bayrak değeri üzerinden değil). Dördüncü bir sızıntı yolu bulunamadı: savepoint geri alması GUC'u geri sarsa bile `finally` yeniden yazıyor; kuyruk işçisi ayrı bağlantı/transaction'da; PgBouncer tarafı transaction sınırıyla kapalı; seviye-0 çağrıda scope kendi transaction'ını açıp COMMIT ile bayrağı düşürüyor.
- **`AuditDriver` paket sözleşmesine uygun.** `mergeConfigFrom` doğrulandı: eksik anahtarlar (`user.morph_prefix`, `empty_values`, `events`, `queue.enable=false`) paket varsayılanından geliyor; `getTable()` override'ı `drivers.database.table='audits'` tuzağını kapatıyor; `prune=false` append-only tabloda doğru davranış.
- **N-1/N4 disiplini hem uygulama hem DB katmanında** (exception + `WITH CHECK`), ve ikisi ayrı ayrı test edilmiş ("belt and braces" testi uygulama katmanını bilerek atlıyor).
- **Kod yerleşimi (AD-25) uygun:** `app/Support/Audit` plan §3.3'ün adresi; `Queries/Audit`, `Listeners`, `Integrations/KamuSM`, `Jobs`, `Http/Middleware` sözleşmeye oturuyor. `PersistsAuditRows`'un N-1 reddi iş kuralı değil, DB politikasının uygulama aynası — Support'ta durabilir.
- Test disiplini temiz: tüm testler gruplu (`omurga`/`omurga-rls`), çıplak `now()` yok, `Http::preventStrayRequests` chunk1 altyapısından geliyor, RLS testleri non-superuser rolde (`rolsuper/rolbypassrls` assert'i testin kendisinde).
- Orphan durumu dürüstçe raporlanmış; commit mesajları AI attribution'sız ve hook formatına uygun; Pint temiz (`--test --dirty` koştum), TeamCity #708 SUCCESS bağımsız doğrulandı.

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

**F-1 — Çalışma ağacındaki mühür/Merkle revizyonu AD-06 ve spec §7.1 ile çelişiyor; kriter 14'ü geri alıyor.** AD-06'nın kararı açık: batch hash'ine **KamuSM RFC 3161 nitelikli zaman damgası** (5070 kapsamında delil niteliği) ve **"Blockchain/zincir yapısı YOK."** Commit'lenmemiş revizyon KamuSM adaptörünü siliyor (`app/Integrations/KamuSM/*` üç dosya), `audit_stamps`'i `merkle_root`/`previous_chain_hash`/`chain_hash`/`witnessed_at` kolonlu bir **zincir** yapısına çeviriyor (`2026_07_26_000100:173-224`) ve tanımsız bir "external witness" kavramı getiriyor. İç zincir + adsız tanık, nitelikli zaman damgasının delil niteliğini vermez; spec §7.1 "nitelikli zaman damgasıyla damgalanır" der. Bu, impl serbestisi değil AD/spec seviyesinde bir karardır. *Failure scenario:* bu hâl commit'lenirse sistem 5070 kapsamında delil üretmeyen bir denetim katmanıyla yaşar ve kriter 14 karşılıksız kalır.

**F-2 — Çalışma ağacı derlenmiyor; hiçbir test bu hali doğrulamadı.** `config/audit.php:4` ve `AuditServiceProvider` silinmiş `NullTimestampProvider`/`TimestampProvider`/`StampAuditBatch`'i import ediyor; `AuditWiringTest` ve `AuditWriteGuaranteeTest` aynı silinmiş sınıflara, `AuditSchemaTest`/`ArchiveAuditBatch`/`AuditStamp` modeli artık var olmayan `app.audit_batch_digest` fonksiyonuna ve `digest`/`stamped_at`/`token` kolonlarına referans veriyor; `SealAuditBatch` hiçbir schedule'dan çağrılmıyor (ORPHAN), `PublishAuditRoots` yalnız yorumda var. TeamCity #708 yalnız `ed65adc`'yi kapsar; mevcut ağaç class-not-found ile düşer.

**F-3 — Plan §3.8 sıkıştırma uygulanmadı; sapma plan tarafında karşılıksız.** Teknik gerekçe doğrulanmış görünüyor (`ERROR: columnstore cannot be used on table with row security`, TimescaleDB 2.28.3/PG17) ve RLS lehine tercih kırmızı çizgiyle tutarlı — ancak plan kanundur ve §3.8 hâlâ "compression 90 gün/7 gün" diyor; sapma ne plana işlendi ne `docs/issues.md`'ye yazıldı (migration yorumu "the deviation is recorded in the plan" diyor, **plan metninde yok**). *Failure scenario:* chunk9 rollout'u veya ileride başka bir implementer §3.8'i okuyup sıkıştırmayı "unutulmuş iş" sanır.

**F-4 — Plan §3.9 continuous aggregate uygulanmadı; yerine geçen mekanizma plan dışı.** Aynı kök sebep (`cannot create continuous aggregate on hypertable with row security`), aynı prosedür eksiği: plan metni hâlâ continuous aggregate diyor; zamanlanmış `RollUpUsageDaily` + `app.roll_up_usage_daily()` çözümü plan revizyonu olmadan girmiş. Korunan değişmezler (`user_id` taşınmıyor, tam distinct sayım) doğru ve testli; eksik olan üretilmiş karar kaydı.

**F-5 — İki (ağaçta: `audit_batch_merkle_root` dahil) SECURITY DEFINER fonksiyon, gerekçe gösterilen G-1 sözleşmesinin lafzını aşıyor.** Chunk2 §2.6 G-kuralları: kapsam dışı **okuma**, "yalnız sayı/toplam/boolean" (G-2), "dinamik SQL üretmez" (G-3), ve plan "omurgada bir örnek bulunmaz, ilk örnekler dikey planlarda" der. Oysa: `app.audit_batch_digest` / `audit_batch_merkle_root` `RETURNS TABLE(...)` + `EXECUTE format(...)` dinamik SQL kullanıyor (`..._000300:39,49`); `app.roll_up_usage_daily` okuma değil **INSERT yapan** bir SECURITY DEFINER (`..._000200:145-176`) — G-1'in hiç kapsamadığı bir sınıf. Allowlist'li tablo kısıtı ve `%I` quoting'i fiilî riski düşürüyor, COMMENT'ler S-9 envanterine hazır; ama fonksiyonların dayandığını iddia ettiği kural onları kapsamıyor. Bu ya plan tarafında G-sözleşmesinin genişletilmesini ya da fonksiyonların sözleşmeye çekilmesini gerektiren bir karardır — karar mercii review değil.

**F-6 — `BackofficeAuditFeed::recordTheRead` gerçekçi filtrelerde `field_class` varchar(64) sınırını aşıyor → backoffice okuması patlıyor.** `AccessLogger.php:41` `field_class`'ı `implode(',', $fields)` ile dolduruyor; kolon `varchar(64)` (`..._000100:125`). Feed `'cross_tenant_audit_read'` + `'rows=N'` + `'filter='.http_build_query(...)` gönderiyor (`BackofficeAuditFeed.php:123-126`). *Failure scenario:* `writes(tenantId: 7, event: 'auth.login')` → `"cross_tenant_audit_read,rows=2,filter=tenant_id=7&event=auth.login"` = 66 karakter → `value too long for type character varying(64)` → sert garanti gereği okuma tamamlanmışken çağrı exception'la düşer. Mevcut testler filtresiz çağırdığı için (38-39 karakter) yeşil.

**F-7 — `LogUsage` plan §3.5'in "yalnız liste/detay rotaları" sınırını uygulamıyor.** `shouldRecord` (`LogUsage.php:40-53`) yalnız GET + eşleşen rota + <300 kontrolü yapıyor; rota sınıfı filtresi yok — sınıfın kendi docblock'u ("only for routes that actually show records") bile kodda karşılıksız. *Failure scenario:* web grubundaki her GET (login sayfası dahil, anonim ziyaretçiler dahil) `audit_usage`'a satır ve günlük rollup'a boyut üretir; kullanım logu sinyali gürültüye gömülür, hacim profili planın öngördüğünün katına çıkar.

**F-8 — `masked_fields` e-postayı içermiyor; AD-16 ve plan §3.7 e-posta maskesini açıkça sayıyor.** `Masker::email()` şekli yazılmış ama `config/audit.php:95-99` listesinde `email` yok; `Masker::apply` yalnız listedeki alanları maskeler. *Failure scenario:* e-posta alanı taşıyan ilk Auditable model (chunk5 `customer_users`) güncellendiğinde ham e-posta silinemez tabloya yazılır ve bir daha çıkarılamaz (AD-16'nın varlık sebebi olan senaryo).

**F-9 — Kriter 7'nin kanıtı eksik: paket driver yolunda hassas alan + string arama testi yok.** Maskeleme yalnız `AuditWriter::record` yolunda sınanıyor; `AuditDriver::audit` yolu (model güncellemesi → `toAudit` → `Masker`) hassas alanlı bir modelle hiç koşulmadı (`AuditProbeModel` yalnız `label` taşıyor). İki yol ayrı kod; biri test edilmemiş.

**F-10 — Kriter 17c'nin kasıtlı ihlal probu yok ve mevcut yüzey testi ihlali yakalayamaz.** `AuditSchemaTest` "carries the cross tenant flag on the evidence tables only" `whereIn('tablename', [üç audit tablosu])` ile sınırlı (`AuditSchemaTest.php:95`): bayrağı **başka bir tabloya** taşıyan bir politika bu testin kapsamına hiç girmez. Kriterin açık şartı — "bayrağı fixture tabloya taşıyan politika testi kırıyor" — karşılıksız. *Failure scenario:* ileride bir migration `app.audit_read_all()`'ı bir iş tablosunun SELECT politikasına eklerse hiçbir test kırılmaz; bayrak genel izolasyon bypass'ına dönüşür (B-3'ün kovduğu durum).

**F-11 — `OfficeAuditFeed` okuma sorgusunu `TenantContext::run()` içine almıyor (chunk2 §2.5 kuralı, B-7'nin lafzı).** Chunk2 §2.5: "Her `App\Queries\*` sınıfı okuma sorgusunu `TenantContext::run()` içine alır." Sınıf (`OfficeAuditFeed.php:33-70`) bağlamı çağırana bırakıyor; kriter 17f testleri bağlamı dışarıdan kuruyor. Güvenlik yönü kapalı (bekçi bağlamsız SELECT'i keser) ama plan deseni ihlalde: bu sınıfın chunk6/8'deki her tüketicisi sarmayı kendisi hatırlamak zorunda kalacak.

**F-12 — Bekçinin yeni `app.` atlaması yalnız `pg_tables`'a karşı sabitlenmiş; view/matview/foreign table açık.** `TenantContextGuard.php:185` `app.` önekli her tanımlayıcıyı atlıyor; güvence testi (`AuditSchemaTest.php:151`, "keeps the app schema free of tables") yalnız `pg_tables`'ı sorguluyor. *Failure scenario:* `app` şemasına eklenen bir VIEW (view'lar `pg_tables`'ta görünmez ve **sahibinin** yetkileriyle koşar — sahip migration rolü `postgres` olduğundan RLS'i tamamen bypass eder) hem bekçiden hem güvence testinden geçer; `SELECT * FROM app.leaky_view` tenant verisini bağlamsız döndürür. Mekanizma bu chunk'ta açıldı, güvencesi mekanizmanın kendisinden dar.

**F-13 — Kriter 11b karşılanmadı ve taşınması plan değişikliği gerektiriyor.** Kriter chunk5 (`customer_users`) ve chunk6 (izinler) olmadan sınanamıyor — index'in "sonraki chunk'a bağımlı kriter yoktur" kuralıyla plan kendi içinde çelişiyor. `docs/issues.md`'ye yazılmış olması doğru davranış; ancak kriter listesinden düşürme/taşıma kararı Sets'indir, implementer'ın issues notu tek başına kriteri kapatmaz.

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

1. **Mühür/Merkle revizyonunun niyeti nedir?** AD-06 hem KamuSM RFC 3161'i emrediyor hem zincir yapısını yasaklıyor. Bu yön isteniyorsa önce AD-06 ve spec §7.1 revize edilmeli (upstream süreç); istenmiyorsa çalışma ağacı `ed65adc`'ye dönmeli. Bu karar verilmeden chunk3 hiçbir halde kapanamaz.
2. Kriter 11b chunk6 §6.9'a mı taşınacak (plan revizyonu), yoksa chunk3'te başka bir kanıtla mı karşılanacak?
3. §3.8/§3.9 sapmaları (sıkıştırma yok, rollup) plan metnine hangi süreçle işlenecek — ve G-1 sözleşmesi digest/rollup sınıfı fonksiyonları kapsayacak şekilde genişletilecek mi (F-5)?
4. İki feed'in orphan durumu chunk6/chunk8'e kadar açıkça kabul ediliyor mu? (CLAUDE.md onay kuralı 4 ↔ planın "iş ekranı üretmez" kararı — çatışmayı Sets'in tutanağa bağlaması gerekir.)
5. `audit_usage` retention süresi migration anında config'ten DB politikasına sabitleniyor; backoffice'ten 2→5 yıl değişikliğinin politikayı güncelleme yolu chunk8'de tanımlanacak mı?

## ⚠️ Upstream Findings

- **Plan §3.8 ve §3.9 yazıldığı haliyle kurulamıyor** (TimescaleDB columnstore ve continuous aggregate, RLS'li tabloyla karşılıklı dışlayıcı — iki hata mesajı da canlı ortamda üretilmiş). Plan revizyonu gerekir; impl'in RLS lehine yönü doğru, ama kararın evi plan.
- **Çalışma ağacındaki zincir tasarımı AD-06'nın açık kararının tersine** ("Blockchain/zincir yapısı YOK" + KamuSM RFC 3161) ve spec §7.1'in "nitelikli zaman damgası" şartının altında. Bu yol isteniyorsa AD/spec turuna dönülür; impl aşamasında verilebilecek bir karar değildir.
- Kriter 11b, index'in "sonraki chunk'a bağımlı kriter yoktur" kuralını ihlal eden bir plan maddesi (F-13).

## Verdict: ❌ REJECTED

Matrix'te ❌/⚠ satırlar var; 13 bulgunun ikisi (F-1, F-2) mevcut ağacı derlenemez ve AD-06'ya aykırı bırakıyor, biri (F-6) doğrulanmış bir runtime hatası. `ed65adc` tek başına dahi F-3…F-13 nedeniyle REJECTED olurdu; ancak açık olmak gerekir: o commit'in çekirdeği — üç log sınıfı, append-only, N-1/N4 disiplini, B-5 bayrak yaşam döngüsü, bekçi muafiyet modeli — sağlam kurulmuş ve gerçek testlerle kanıtlanmış. Kalan iş ağırlıkla (a) mühür revizyonu kararının upstream'de bağlanması, (b) iki sapmanın plan tarafında resmileştirilmesi, (c) F-6/F-7/F-8 düzeltmeleri ve üç test boşluğudur.
