# Core/Render/Resolvers — ad → yol/meta/payload çözümleyiciler (10 dosya) > **Doğrulanan kod tabanı:** `c23b431f` · **Tarih:** 2026-10-05 · **Yayın:** 0.9.7 = bu commit + sonrası; belge yalnız doğrulama anındaki kodu anlatır > **Kaynak klasör:** `Core/Render/Resolvers/` — **10 `*.php`** = 6 kök + `Sub/` altında 4. > **Envanter:** 10 dosyanın **10'u** anlatıldı. > **Doğrulama platformu:** Windows + PHP 8.3, `ViewResolver`/`LayoutResolver`/`RobotsResolver` gerçekten çalıştırılarak. ## 1. Ne işe yarar, kim kullanır Resolver'lar **okuma tarafıdır**: veritabanı/ayar/dosya sistemi bilgisini alıp "bu mantıksal ad neye karşılık gelir?" sorusunu tek yerde yanıtlarlar. Hiçbiri HTML basmaz (basma [Providers](Providers.md)'ın işidir) ve hiçbiri istek başlatmaz. **Erişim:** `$this->resolver('')` (`DiscoveryActionsTrait.php:154`) — tümü `SystemRenderMapTrait.php:69-80`'de `resolvers` altında kayıtlı. **Tuzak:** `handler('schema')` **değil** `resolver('schema')` doğrudur (`SystemLogicMapTrait.php:114` yanlış kayıt; bkz. [README §4](README.md)). ## 2. Klasör/dosya envanteri (10/10) | Dosya | Görev | Önemli public yöntemler | |---|---|---| | `ViewResolver.php` (126) | **Mantıksal view adı → fiziksel dosya yolu.** 7 hedef dizini sırayla tarar, `.php` ve `.rbn.php` uzantılarını dener. | `resolve(string $view,array $options=[]): ?string` · statik: `$resolveCache` | | `LayoutResolver.php` (97) | Layout zinciri + **bölüm yığını** (`@section`/`@yield`). `ob_start()` tabanlı. | `setExtends(string $layout): void`, `getExtends(): ?string`, `resetExtends(): void`, `startSection(string $name): void`, `endSection(): void`, `getSection(string $name,string $default=''): string`, `getAllSections(): array`, `clear(): void` | | `SeoResolver.php` (479) | **Meta birleştirici.** `SettingsService` grupları + `SeoConfig::defaults()` + controller override'ları → `meta`/`og` paketi. **noindex'in tek yorumlayıcısı** (bkz. §7 noindex maddesi). | `resolve(string $context='project',array $overrides=[]): array`, `resolveFaviconRaw(array $hub,array $overrides=[]): string`, `resolveAsset(?string $path,string $virtualName='favicon.svg'): ?string`, `getVirtualResourceRaw(string $name,string $context='project'): string`, `webContext(): array`, `getSeoConfig(): array`, `isSiteNoindex(): bool`, `isPageNoindex(array\|object $entry): bool` · statik: `directiveHasNoindex(mixed $directives): bool` · özel: `siteRobotsDirective(): mixed` · korumalı: `formatFaviconData()`, `resolveSocial()` | | `SchemaResolver.php` | JSON-LD bağlamı: sayfa meta'sı (`url` = canonical), kurum verisi (ayarlar + controller), varsayılan görsel (= sayfanın `og:image`'i). Breadcrumb URL yolundan türetilmez. | `meta(): array`, `company(array $data=[]): array`, `organizationId(array $company=[]): string`, `resolveDefaultImage(): string` | | `BreadcrumbResolver.php` (85) | Kırıntı adımlarını modül metadata'sından türetir. | `resolve(array $options=[]): array`, `resolveInitialMetadata(array\|object $moduleData,string $module,array $options): array`, `resolveSegmentMatch(string $segment,array $subModules): ?array`, `normalizeIcon(string $icon): string` | | `CrawlerResolver.php` | `robots/sitemap/feed` yapılandırmasını proje kaynaklarından okur. **Listeleme TEK üretici:** `resolveListing()` sitemap, feed ve llms.txt için girdileri verir (sayfa noindex, yönlendirilen/panel yolu, `robots.txt` Disallow kuralına takılan, dış/sorgulu/parçalı adres ve yinelenen elenir); adres `resolveEntryPath()` ile bir kez kurulur. | `resolveCrawlerMap(): ?array`, `resolveProjectSources(): array`, `resolveEntryPath(array\|object $entry,array $source,array $categoryMap): ?string`, `resolveListing(array $source,array $sources,string $methodKey,string $defaultMethod,int $page=1): array`, `resolveDirectModel(string $identifier,string $moduleNamespace): string`, `callServiceMethod(array $config,string $methodKey,string $defaultMethod,...$args)` | | `Sub/RobotsResolver.php` | `robots.txt` yük verisini ve yol kısıtlamasını üretir. Geliştirme/uyumsuz host `Disallow: /`; site noindex üretim kurallarını yazar (yalnız `Sitemap:` satırı yok). `productionRules()` robots.txt'in yayınladığı TEK kural kümesidir; `isAllowedBy()` (RFC 9309: en uzun eşleşme, eşitlikte Allow, `*` ve `$`) ve `isPathDisallowed()` sitemap/feed/llms çıktısını aynı kümeye karşı denetler. | `resolvePayload(): array`, `productionRules(): array`, `mergeRules(array $payload): array` (statik), `isAllowedBy(array $rules,string $path): bool` (statik), `isPathDisallowed(string $path): bool`, `isPathRestricted(string $path): bool` · korumalı: `isNonProduction(): bool`, `isSiteNoindex(): bool`, `panelRoots(): array` | | `Sub/SitemapResolver.php` | Sitemap girdilerini sayfalar + model verisinden toplar (`loc` + `lastmod`; priority/changefreq yok). Kaynak girdileri `CrawlerResolver::resolveListing()` verir. `getSummary()` indeks girdisine her alt sitemapin en büyük `lastmod` değerini yazar; alt sitemap 50.000 girdide bölünür. | `resolvePayload(string $view): array`, `getSummary(): array`, `getData(string $type,int $page=1,bool $onlyCount=false)`, `resolveCorePages(): array` · korumalı: `hasRouteContent(string $cleanUri): bool`, `sourcesSatisfied(array $kaynaklar): bool` | | `Sub/FeedResolver.php` | RSS kanal + gönderi listesini hazırlar (en çok 30 öğe); girdiler `CrawlerResolver::resolveListing()` ile gelir (feed `link` = sitemap `loc` = canonical). | `resolvePayload(): array` | | `Sub/AdsTxtResolver.php` | `/ads.txt` yük verisi: yayıncı kimliği YALNIZ proje verisinden gelir (`project-routemap.php` → `view_mapping[site]['adsense_publisher_id']`, biçim `pub-` + 16 hane = `data-ad-client` değerinin `ca-` önekisiz hali). Anahtar yoksa `null` (dosya yok, `/ads.txt` 404); biçimi bozuksa `null` + `error_log` (bozuk kimlik doğrulamayı bozar, yayınlanmaz). Fiziksel `ads.txt` dosyası okunmaz. | `resolvePayload(): ?array` · sabitler: `PUBLISHER_ID_PATTERN`, `SYSTEM_DOMAIN`, `RELATIONSHIP`, `CERTIFICATION_AUTHORITY_ID` | | `Sub/LlmsResolver.php` | `llms.txt` bölüm/girdi listesini hazırlar; sayfa bölümü `resolveCorePages()`, kaynak bölümleri `resolveListing()` çıktısıdır. Blockquote özeti `CrawlerMap['brand']['description']` değeridir (yoksa blockquote yazılmaz). | `resolvePayload(): array` | ## 3. Akış — `ViewResolver::resolve()` (ViewResolver.php:34-125) Sırayla denenen **7 hedef dizin** (`:49-90`): ``` resolve('auth/login', ['context'=>'frontend']) ├─ TARGET 0: Paths::module($module)->views() (aktif modül) :55-60 ├─ TARGET 1: Paths::project()->views() (proje — her şeyi ezerer) :63 ├─ TARGET 2: Paths::module($panel,'suite')->views() (aktif panel paketi) :66-71 ├─ : Paths::module($context)->views() (bağlam keşfi) :74-76 ├─ TARGET 3: Paths::framework()->views() :79-80 ├─ : frameworkViews . '/RbnCommon' :81 ├─ : frameworkViews . '/' :84-86 └─ : frameworkViews . '/' (context≠frontend) :88-90 ``` **Önce iki hızlı yol denenir, dizin taraması yapılmadan:** 1. `RbnCommon/` doğrudan dosya kontrolü (`:99-105`) — en sık kullanılan ortak şablon için. 2. Keşif haritası (`$mapper->get("view:{$cacheKey}")`) — önceki çözümün **kalıcı** kaydı (`:107-111`). Sonra dizinler sırayla taranır; bulunan ilk yol **kalıcı haritaya yazılır** (`:113-122`) ve statik `$resolveCache`'e konur. **Ölçülen sonuç:** `Paths::init('E:/localhost/projects/', …)` ile `resolve('errors/404'|'layouts/master'|'auth/login', ['context'=>'frontend'])` → **üçü de `NULL`**. Neden: framework `Resources/Views/` altındaki gerçek dosya adları `Errors/development.php`, `RbnAuth/auth.rbn.php` gibi **büyük harfli klasör adı + farklı dosya adı** taşıyor; mantıksal ad ile fiziksel yol eşleşmiyor. `ViewResolver::resolve()` bu ikisini bağlayan bir kural içermiyor — çağıran (`FrontendProvider`) `Layouts/header`, `Layouts/footer` gibi adları deniyor ve `file_exists()` kontrolü yok dönüşünde `handleMissing()` çağrılıyor (`Providers/UI/FrontendProvider.php:85-87`). Ölçülen çerçeve `projects/` üzerinde, `framework` tarafındaki `Resources/Views` (29 dosya) tarandı; ancak `Resources/Views/RbnCommon/header.rbn.php` gibi dosyalar **`RbnCommon/` önekiyle** eşleşir, düz `header` ile değil. Ayrıntı: [acik-sorular.md §1.10](../../acik-sorular.md). ## 4. Akış — `LayoutResolver` bölüm yığını (LayoutResolver.php:53-78) `@section('ad')` → `$layout->startSection('ad')` → `ob_start()` + yığına adı (iterasyon `:55-57`). `@endsection` → `endSection()` → `array_pop` + `ob_get_clean` (`:63-70`). `@yield('ad')` → `getSection('ad')` (`:75-78`). Ölçülen yığın davranışı (V6): ``` startSection('a'); echo 'A1'; startSection('b'); echo 'B1'; endSection(); echo 'A2'; endSection(); getSection('a') = 'A1A2' ← iç içe doğru birleşti getSection('b') = 'B1' getAllSections() anahtarları = 'b,a' ← önce kapanan (LIFO) önce ``` **Tuzak (asıl önemli nokta):** `View::result()` içindeki `$layoutResolver->getExtends()` + `ViewEngine::render()` zinciri (§ README 3.2) **bu bölüm yığınını otomatik doldurmaz.** Çünkü iç içe render'da `ob_start()` çağrıları iç içe geçer ve `@section` çağrıları yalnız **view dosyasının kendi buffer'ında** toplanır. Yani `@extends` + `@section` + `@yield` üçlüsü teorik olarak var ama **mevcut korpusta kullanılmıyor**: * Ölçüm: `projects/` + `domains/` + framework `Resources/Views/` altındaki **1040 `.php` dosyasında** `@extends(` **0**, `@section(` **0**, `@yield(` **1** (`projects//Modules/Frontend/Email/Views/app.rbn.php:10`), `@import(` çok sayıda. * Tek `@yield('content')` kullanımı bir **panel e-posta istemcisi ekranıdır** ve `@section` üreten bir dosya yoktur → `getSection('content')` her zaman `''` döner ve o `
` **boş basılır**. **Sonuç:** Layout sarmalama pratikte **`@import` ile parça birleştirme** üzerinden yapılıyor (parçalı landing sayfaları: `projects/customers//Modules/Frontend//Views/home.rbn.php:1-7`), `FrontendProvider` ise `Layouts/header` / `Layouts/footer`'ı **ayrı `render()` çağrılarıyla** basıyor (`Providers/UI/FrontendProvider.php:97-105`). `.agents/rules/core-architecture.md` §6.2'nin `$content` iddiası kodla çelişiyor ve bu ölçümle **üçüncü kez** doğrulanmış oldu ([acik-sorular.md §1.3](../../acik-sorular.md)). ## 5. Akış — `SeoResolver::resolve()` öncelik zinciri (SeoResolver.php:21-138) ``` SeoResolver::resolve($context, $overrides) ├─ settings->read('seo'|'company'|'social'|'contact'|'appearance') :26-31 │ ($isLive = context ∈ {project, frontend, panel}; değilse seoDb boş) ├─ $hub = {config, seo, company, social, contact, appearance, context} :33-41 ├─ Başlık: overrides.title > seo['meta-title'] :46 │ · siteName: appName > config.app_name > company-name > FRAMEWORK_NAME :47-53 │ · boşsa/site adıysa tam SEO başlığı; değilse "Başlık | SiteAdı" :55-68 ├─ Açıklama: overrides > meta-description > company-slogan > config.app_slogan > defaults :70-78 ├─ keywords: overrides > meta-keywords > defaults :80-82 ├─ robots: overrides > meta-robots > defaults :84-86 ├─ module-name: overrides > appName > company-name > config.app_name > 'RBN Project' :90-98 ├─ module-version: overrides > ProjectVersionResolver::resolve(...) :104-106 ├─ resolveFaviconRaw($hub, $overrides) :109 └─ ['context','meta','og','label','copyright'] :131-137 ``` **Kritik:** `$overrides['title']` **her zaman** önceliklidir — `View::metaseo()` tam olarak bu yolu kullanır (`View.php:88-96` → `SeoService::prepare` → `SeoBuilder::prepare` → `SeoResolver`). ## 6. Yapılandırma ve varsayılanlar | Sabit/değer | Değer | Kaynak | |---|---|---| | `SeoConfig::NAME` / `VERSION` | `RbnSeo` / `1.3` | `Configs/SeoConfig.php:18-19` (ölçüldü) | | `SeoConfig::SLOGAN` | `'Professional SEO and Metadata Service'` | `SeoConfig.php:20` | | `RobotsConfig::MAX_BYTES` / `SPECIAL_CRAWLERS` | `512000` / AdsBot-Google, AdsBot-Google-Mobile, Mediapartners-Google, APIs-Google | `Configs/RobotsConfig.php` | | `RobotsConfig::SYSTEM_DISALLOWS` / `CORE_DISALLOWS` / `ASSET_DISALLOWS` | sistem/çekirdek/varlık yasakları | `RobotsConfig.php:33, :63, :109` | | `ViewResolver` uzantı sırası | `['.php', '.rbn.php']` | `ViewResolver.php:43` | | `ViewResolver` keşif anahtarı | `"view:".md5($view.serialize($options))` | `ViewResolver.php:36, :95` | | `LayoutResolver` sistem korumalı bölüm | yok — `clear()` elle çağrılır | `LayoutResolver.php:91` | ## 7. Tuzaklar ve kurallar (ölçülmüş) 1. **`ViewResolver` hedef 1 (proje) her şeyi ezer** (`:63` yorumu: "Overrides everything else"). Yani modül/paket içindeki bir şablon adı, proje `Resources/` altında aynı adla varsa **proje kazanır**. 2. **`isPathRestricted()` ölçülen sonuçlar** (V5): | Yol | Sonuç | |---|---| | `/admin` | `true` | | `/storage` | `true` | | `/vendor/x` | `true` | | `/.env` | **`false`** | | `/index.php` | `false` | | `/rbn-admin/panel` | **`false`** | | `/cpanel` | **`false`** | | `/` | `false` | Yani `RobotsConfig` **`.env`, panel öneki (`dashboard_prefix`) ve `/cpanel` yollarını kapsamıyor**. `ALLOWED_PATHS` (`RobotsConfig.php:117`) yalnız `SYSTEM_DISALLOWS`+`CORE_DISALLOWS`+`ASSET_DISALLOWS` ile birleşiyor; panel öneki `RouteBlueprint::DASHBOARD_PREFIX` üzerinden ayrı yönetiliyor ve bu resolver'a **girmiyor**. `.env` dosyası `public` kökünde değilse sorun yoktur; panel öneği ise gerçek bir boşluktur. *(Kod değiştirilmedi — salt okunur.)* 3. **`RobotsResolver::resolvePayload()` `Paths` kullanır** (`RobotsResolver.php:9`) → `Paths::project()` **init öncesi** çağrılırsa `LogicException` verir ([System/Paths.md §5.4](../System/Paths.md)). 4. **`CrawlerResolver::callServiceMethod()` değişken metot adı çağırır** (`:72`) — güvenlik sınırı **yapılandırma katmanındadır**, kodda allow-list yok. `crawler` yapılandırması `projects` tablosundan geldiği için güvenlik `crawler_map` yazan kişinin yetkisine bağlıdır. 5. **`LayoutResolver::$sections` ve `$sectionStack` statik DEĞİL**, örnek başına yaşar; `ViewEngine` `cluster('view_engine')` **tekil** olduğu için aynı istek içinde bölümler birikir. İstek sonu temizlenmez → uzun süreli CLI süreçlerinde birikim mümkündür (`clear()` çağrılmıyor, arama sonucu boş). 6. **`SchemaResolver::meta()` DB'ye gider** (`Core\Base\Services\BaseService` kullanır) → schema render'ı aktif proje DB'si açık değilse boş döner. 7. **noindex tek kaynak (FW-094-NOINDEX).** Robots/sitemap/llms/feed AYNI iki yardımcıya bakar: `SeoResolver::isSiteNoindex()` (site) ve `SeoResolver::isPageNoindex($kayıt)` (sayfa); `CrawlerProvider` yalnızca devreder. **Site noindex** = `view_mapping[site]['robots']['noindex'] = true` (`getSeoConfig()['robots']['noindex']`) VEYA panel `seo.meta-robots` değeri `noindex` içeriyor. Sonuç: sitemap/llms/feed 404, `robots.txt` üretim kurallarıyla yayınlanır ama `Sitemap:` satırı yoktur (`Disallow: /` YOK: `noindex` ancak taranabilen sayfada okunur). **Sayfa noindex** = kayıt satırında `robots` / `meta_robots` / `meta-robots` `noindex` içerir ya da `noindex` bayrağı doludur; site normal yayın yapar, yalnız o kayıt sitemap + llms.txt + feed'e girmez. ⚠️ Denetleyicide çalışma anında verilen `$this->noIndex()` çağrısı meta etiket ve `X-Robots-Tag` başlığını sağlar ama tarayıcı rotalarının (sitemap vb.) GÖREBİLECEĞİ kalıcı kayıt DEĞİLDİR; site genelini kapatmak için yukarıdaki yapılandırma anahtarı yazılmalıdır. Önbellek: bkz. `Controllers.md` (site noindex önbellekten önce karar verilir); sayfa noindex değişimi çıktıya orta katman önbellek süresi (TTL) kadar gecikmeyle yansır. 8. **Sitemap girdisi iki katmanlı bir filtreden geçer.** Katman 1 mevcut süzgeçlerdir (GET · `{`/`(` yok · `modal` değil · modül namespace'i · `isPathRestricted()` · `/` değil). Katman 2 **`hasRouteContent()`**'dir: *içeriğe bağlı* standart rotalar (`CONTENT_BACKED_ROUTES`) yalnızca gerçekten içerik varken listelenir. Bunun iki çekirdek kuralı vardır: * **`blocked-by:shadowed-page`** (engelleyici) — slug'ı DB'de aktif sayfa olarak varsa `RedirectManager::redirectOldUrls()` o yolu kalıcı olarak `/sayfa/{slug}` adresine 301'ler; rota **hiçbir zaman 200 dönmez** ve kanonik adres zaten listelendiği için rota listelenmez. * **`satisfied-by:faq`** (olumlu) — aktif SSS satırı varsa rota kendi içeriğini 200 ile render eder ve listelenir. Sayfa da SSS de yoksa denetleyici ana sayfaya yönlendirir (302) → listelenmez. **Kutup kuralı** (`sourcesSatisfied()`): kaynaklar `|` ile ayrılır ve her biri `blocked-by:` ya da `satisfied-by:` önekiyle kutbunu söyler. Tetiklenen herhangi bir engelleyici rotayı dışlar; olumlu kaynak tanımlıysa en az biri sağlanmalıdır; yalnız engelleyici tanımlıysa ve hiçbiri tetiklenmediyse rota listelenir. Yani engelleyici kaynak tek başına "listele" demez ama yokluğu da rotayı dışlamaz. Kural **çekirdek standardıdır**; projeler kendi başına özelleştirmez. Sorgu hata verirse rota listelenmez (güvenli taraf: eksik sayfa, bozuk adres değil). `resolveCorePages()` ayrıca **sessiz `catch` kullanmaz**: rota tablosu okunamazsa `error_log('[RBN-CRAWLER] …')` yazar. 9. **`isPathRestricted()` panel/giriş köklerini de kısıtlı sayar** (`panelRoots()`): `RouteBlueprint::AUTH_ROOTS` + çekirdek web rotasının da yönlendirme altına aldığı `giris`. Bu aynı metot üç yerden çağrılır — sitemap girdisi, robots disallow listesi ve trafik istatistiği — dolayısıyla panel yollarının üçünde de tutarlı biçimde elenir. ## 8. Örnek (gerçek koddan) ```php // Core/Render/Resolvers/LayoutResolver.php:53-70 public function startSection(string $name): void { ob_start(); $this->sectionStack[] = $name; } public function endSection(): void { if (empty($this->sectionStack)) { return; } $name = array_pop($this->sectionStack); $this->sections[$name] = ob_get_clean(); } ``` ```php // Ölçülen çıktı — gerçek korpusta @yield tek kullanım // projects//Modules/Frontend/Email/Views/app.rbn.php:8-11 //
// @yield('content') //
← 'content' bölümü hiç doldurulmuyor → boş basılır ``` ## 9. İlgili belgeler * [Core/Render genel](README.md) · [Providers](Providers.md) · [Builders](Builders.md) · [Configs](Configs.md) · [Handlers](Handlers.md) * [Core/System/Discovery.md](../System/Discovery.md) (keşif motoru) · [Core/System/Paths.md](../System/Paths.md) · [Core/Base/Data.md](../Base/Data.md) (`SettingsService`) * [Kavram: yapılandırma](../../kavramlar/02-yapilandirma.md) · [Açık sorular §1.3 / §1.10](../../acik-sorular.md) ## 6. Crawler çıktıları — docs uyumu (FRW-8) Kaynak: `E:\localhost\docs\` (resmi kaynaklara dayalı talimatlar). Çıktıları framework üretir; proje yalnız VERİ verir (`CrawlerMap`, routemap `robots`), sitemap/feed/robots/llms.txt çıktısını elle yazmaz. * **Sitemap** (`google-arama/sitemap.md`, `tarih-kurallari.md`, `search-console/sitemap-gonderimi.md`): UTF-8, mutlak ``, `'` kaçışı, 50.000 URL / 50 MB sınırı ve sitemap indeksi, yalnız kanonik + 200 + `robots.txt`'e takılmayan + noindex olmayan URL, `` yalnız gerçek `modified_at`, indeks `lastmod` = alt dosyanın en yeni girdisi, `priority`/`changefreq` yok, ping yok. * **robots.txt** (`google-arama/robots-txt.md`, `araclar-ve-botlar/*`): `*` ve Google özel tarayıcıları için aynı kurallı gruplar, `Crawl-delay` yok, `/ads.txt` engellenmez, `Sitemap:` mutlak URL, LF satır sonu, 500 KiB sınırı. * **Feed** (`google-arama/rss-besleme.md`): RSS 2.0, `atom:link`, `link` = `guid` = kanonik URL, RFC 822 `pubDate`, tek `charset`. * **llms.txt** (`yapay-zeka-aramalari/llms-txt.md`): `llms.txt` bir ÖNERİDİR (llmstxt.org); resmi standart değildir. Google Arama görünürlüğü için gerekli değildir ve hiçbir yapay zeka platformunun bu dosyayı okuduğu doğrulanmamıştır; bu yüzden framework onu görünürlük vaadi olarak sunmaz. Biçim: tek H1, isteğe bağlı blockquote, H2 + `[ad](url)`, ikincil bağlantılar `## Optional` altında. * **IndexNow** (`yapay-zeka-aramalari/bing-copilot.md`): anahtar dosyası içeriği = anahtar; `{anahtar}.txt` yalnız projenin kendi anahtarı için servis edilir; tek POST en çok 10.000 URL, yalnız aynı host'a ait URL'ler (`IndexNowService::batchesFor()`). * **ads.txt** (`adsense/ads-txt.md` kural 2-4): `/ads.txt` fiziksel dosya DEĞİLDİR; `CrawlerController::ads()` yayıncı kimliğinden üretir: `200`, `text/plain; charset=utf-8`, tek satır `google.com, pub-<16 hane>, DIRECT, f08c47fec0942fa0` (LF). Kimlik proje verisidir (`view_mapping[site]['adsense_publisher_id']`); anahtar yoksa ya da biçim bozuksa `404` (AdSense kullanmayan site: dosya yok). `/ads.txt` `robots.txt`'te engellenmez (`RobotsConfig::ALLOWED_PATHS`) ve sitemap'e girmez. Ortamdan bağımsızdır: yerel ve canlı AYNI içeriği verir. Kapı: `fw_frw12_ads_txt_ortam_robots.php` (`G-ads-01..06`). * **Ortam davranışı — yerel/test ≠ canlı robots (tek kaynak `RobotsResolver::isNonProduction()` → `is_local()`/alan adı uyumu):** yerel/test ortamında (`is_local()`, `*.rbncore.tr` ya da resmi alan adı ailesi dışı host) `robots.txt` `*` ve dört Google özel tarayıcı grubunun HEPSİNDE `Disallow: /` yazar (başka `Disallow`/`Allow` satırı yok) ve `Sitemap:` satırı YOKTUR; `IndexNowService::ping()` yerelde atlanır (bildirim gitmez). Canlıda (`is_local()` false) üretim kuralları ve mutlak `Sitemap:` satırı yazılır. Bu bilerek ve kalıcı davranıştır: yerel site arama motoruna kapalıdır, geliştirme verisi dizine girmez. Tek istisna ortamdan bağımsız çıktılardır (`/ads.txt` gibi: yerel = canlı). Kapı: `fw_frw12_ads_txt_ortam_robots.php` (`G-env-01..03`). * **Doğrulanmadı / kapsam dışı:** `ads.txt` içeriğinin AdSense hesabıyla eşleşmesi (kimlik hesaptan kopyalanır), `Google-Extended`/AI botu kararı (site sahibinin), Bing `crawl-delay`.