# ADR-043 — Áreas restritas invisíveis para indexação (CMS, API e futuras)

| Campo | Valor |
|---|---|
| **Status** | Aceito (2026-10-20) |
| **Data** | 2026-10-20 |
| **Plano de origem** | decisão do dono (revisão B5, 2026-10-20) |
| **Relacionadas** | ADR-005, ADR-020, ADR-022, ADR-024, ADR-027, ADR-033, ADR-035, ADR-037 |

## Contexto

O V7 separa superfícies por **domínio**: o portal público em `[base]`, o painel em `cms.[base]`, a
API em `api.[base]` e quaisquer áreas futuras (suporte, integrações, backoffice) em subdomínios
próprios. O portal é a única superfície feita para pessoas e buscadores; as demais são **restritas**
— exigem autenticação, permissão, auditoria e postura de segurança mais forte.

O risco é de **mapeamento**: um `robots.txt`, `sitemap.xml`, manifest, JSON-LD, canonical, `hreflang`
ou link de navegação que cite uma área restrita transforma uma superfície privada em alvo público.
Isso viola o princípio de menor exposição (ADR-020) e pode vazar estrutura interna mesmo quando a
autenticação está correta.

## Decisão

1. **Só o portal é indexável.** `robots.txt` público libera o portal e aponta apenas o sitemap do
   portal. Todas as demais áreas são **RESTRITAS e INVISÍVEIS por padrão** para qualquer sistema de
   indexação/mapeamento.
2. **Nunca mencionar áreas restritas em mapeamento.** `robots.txt`, `sitemap.xml`/`sitemapindex`,
   manifest, canonical, `hreflang`, JSON-LD, feeds e qualquer outro artefato de descoberta **não**
   listam, linkam ou descrevem `cms.[base]`, `api.[base]` ou áreas futuras.
3. **`robots.txt` de área restrita bloqueia tudo** (`Disallow: /`) e **não** aponta o sitemap do
   portal. O portal **não** ganha uma linha `Disallow: /cms`: a ponte pública não é mapeada; ela
   responde com `X-Robots-Tag: noindex, nofollow, noarchive`.
4. **Sitemap é fechado por padrão.** O `BuildSeoSitemap` só aceita URLs do **host do portal** e
   rejeita caminhos de área restrita (`/cms`, `/api` e equivalentes). Uma fonte de módulo/service que
   tente contribuir URL restrita é **filtrada na saída** — a garantia é executável, não disciplina.
5. **Segurança mais elevada por padrão.** Toda área nova nasce com: `X-Robots-Tag: noindex,
   nofollow, noarchive`, HTML privado (`must-revalidate, no-cache, no-store, private`), CSP mais
   estrita no edge, autenticação/permissão próprias e auditoria (ADR-024/033/035/037). Só uma ADR
   explícita pode promover uma área a pública/indexável.
6. **A ponte pública é única e sem mapeamento.** O domínio base expõe apenas `/[base]/cms[/...]`
   como redirecionamento para `cms.[base]` (ADR-005/ADR-037); ela não é uma página, não entra em
   sitemap/robots e não espelha rotas do painel.
7. **Descoberta de área restrita é interna.** O CMS conhece os próprios módulos/services pelo
   manifesto e pela navegação autenticada; isso nunca é publicado para o portal.

## Justificativa (alternativas descartadas)

- **Listar `/cms` no `robots.txt` do portal com `Disallow`** — descartada: o próprio `robots.txt`
  passa a revelar a área restrita e o caminho; a resposta correta é `X-Robots-Tag` na ponte e
  `Disallow: /` no domínio restrito.
- **Confiar apenas na autenticação** — descartada: autenticação não impede indexação de metadados,
  redirects e URLs vazadas em sitemap.
- **Detectar a área pelo `Host` dentro do service Seo** — descartada: a rota já pertence à área
  (`AreaRegistrar`); detecção por host duplicaria a verdade e criaria ramo por ambiente.
- **Filtrar no módulo que contribui URLs** — descartada: um módulo novo pode esquecer; o filtro
  pertence ao **dono do XML**, que é o service Seo.

## Consequências

**Positivas**
- Superfície pública mínima: buscadores nunca descobrem o painel, a API ou áreas futuras por
  mapeamento.
- Garantia testável no service (filtro de sitemap) e no edge/app (robots e headers), sem depender de
  revisão manual.
- Regra única para qualquer área futura: nasce restrita; promoção exige ADR.

**Custos / negativas**
- Uma área que um dia precise ser pública exigirá ADR e ajuste explícito de `robots`/sitemap.
- O filtro do sitemap precisa conhecer o host do portal (`SeoSettings::baseUrl()`); mudança de
  domínio exige revisão de configuração (já é assim para canonical/`hreflang`).

## Exemplo mínimo (comparativo)

```php
// ❌ Fonte de sitemap que vaza a área restrita
return [['loc' => 'https://cms.exemplo.gov.br/noticias']];

// ✅ Service Seo filtra na saída: só host/path do portal entram
// BuildSeoSitemap::publicUrls() rejeita cms./api. e /cms, /api
```

```text
# ✅ cms.[base]/robots.txt
User-agent: *
Disallow: /

# ✅ portal/robots.txt (não cita a área restrita)
User-agent: *
Allow: /
Sitemap: https://[base]/sitemap.xml
```

## Verificação

- `SeoSitemapTest::restricted_areas_never_leak_into_the_sitemap` (fonte de teste tenta vazar
  `cms.`/`api.`/`/cms`/`/api` e o XML não as contém).
- `SeoRootFilesCoverageTest::the_private_areas_disallow_all_robots` e o `robots.txt` do portal sem
  menção a áreas restritas.
- `CmsDomainRedirectTest` prova a ponte única e o `X-Robots-Tag: noindex, nofollow, noarchive`.
- `EnforceSecurityHeaders` aplica `X-Robots-Tag` e cache privado a toda área `isPrivate()`.
