# Plano 01 — Análise das Referências e Decisões Arquiteturais

> **Milestone:** M0 · **Depende de:** — · **Habilita:** 02–15
> **Objetivo:** consolidar o aprendizado dos 5 projetos de referência em um pacote
> de ADRs enxuto que será a "constituição" técnica do Public V7.

---

## 1. O que cada referência ensina

### 1.1 `cms/` — Concordia (implementação conceitual, a mais avançada)
**Adotar:**
- Estrutura DDD na raiz (`core/ services/ layouts/ modules/`) + autoload PSR-4 por camada.
- Specs como fonte da verdade (`specs/0..9`) e planos com DoD (`steps/`).
- Golden paths: `Result<T>` (não adotado), RFC 7807, `HasAuditLog` (não adotado),
  `ShreddableCast` (não adotado), stubs, `make:module`.
- `ModuleDiscoveryService` com manifesto em cache (`bootstrap/cache/modules_manifest.php`) — zero DB no boot.
- `LayoutPreset` como Enum tipado governando regiões do layout; grid 21/55/21 com subgrid.
- `AppLayout` (Blade Component) como orquestrador de regiões e overrides.
- Observabilidade OTel + Vector + Loki/Tempo; debugging por `X-Request-ID` UUIDv7.
- `Makefile` como interface única; perfis de compose (`telemetry`, `debug`, `sonar`).

**Evitar / ajustar:**
- **Complexidade de 10 imagens** no dia zero — manter núcleo enxuto, crescer sob demanda.
- **Inconsistência de rigor**: specs citam PHPStan L9, `phpstan.neon` está em L7. Aqui: nível máximo, explícito.
- **Filament v4** (que internamente usa **TipTap**): pesado e de acessibilidade difícil de garantir; ver ADR-009. O **TipTap é mantido** como editor único (ADR-028).
- Criptografia pesada (YARA/WORM) antes de haver dado real — entra como trilha opcional. **BLAKE3 é adotado** (ADR-029; integridade/auditoria, extensão PHP pinada).

### 1.2 `sucesu-cms/` — V2 (implementação concreta em tecnologia antiga)
**Adotar:**
- **Auto-descoberta de módulos** por varredura de diretório (`ModuleProvider`), com agrupamento e manifesto — validado em produção.
- **Layout configurável por banco**: `v2_portal_configs` (tema/SEO) + `v2_layout_base_configs` (logos, contato, redes) injetados por ViewComposer; tokens `--tenant-*`.
- **Widget de acessibilidade** completo (alto contraste, escala de fonte, VLibras) e páginas de erro de portal.
- **E2E Playwright em projeto isolado** (`tests-e2e/`) com *histories* `.md` 1:1 com specs, e **sniffCSS** (CSS computado + WCAG) como gate visual/a11y.
- `MODULE.md` por módulo; convenções grep-friendly; logs de atividade.
- Lição de dívida: 828 arquivos sem fronteira rígida → códigos órfãos (ExcelImporter) e exceções não documentadas.

**Evitar:**
- Restrições do legado PHP 7.4/Laravel 5.5 (não são mais necessárias).
- Crescimento de serviços sem contrato de fronteira; exceções silenciosas.

### 1.3 `prefeituras_v5/` — o alvo da reescrita
**Adotar (domínio):**
- Inventário de módulos: Licitações, Contratos, Diário Oficial, Notícias, Obras, Ouvidoria,
  Licitações/Propostas, Concursos/PSS, Portais da Transparência, Forms dinâmicos, Servidores, Estagiários.
- Requisitos de SEO/legado: preservação de URLs, redirecionos 301, sitemaps, `robots.txt`.
- Integração de mídia (imagens, PDFs do diário), certificados digitais, geolocalização/PostGIS.
- Checklists e guidelines de **acessibilidade eMAG** já produzidos pela equipe.

**Evitar:**
- Estrutura `app/Http/Model`, repositórios sem contrato forte, views gigantes, JS espalhado.
- Dependência de edição em `vendor/` (spatie activitylog) — nunca mais.

### 1.4 `tecvoto-monorepo/` — Docker no mundo real
**Adotar:**
- **Topologia de deploy por cliente**: Apache (proxy reverso cPanel :443) → container Nginx → php-fpm/worker, com porta interna única por instância.
- Hub central de identidade para multi-tenant (primeiro login resolve a instância).
- Backups `pg_dump -Fc` agendados + retenção; `.pgpass`; runbook de restauração.
- CI/CD em runner shell com deploy por `git pull` + rebuild; gotchas documentados.

**Evitar:**
- `config:cache`/`route:cache` quando há troca dinâmica de contexto em runtime.
- ModSecurity/WAF ligado em `/cms` (quebra HTML de editor) — resolver com CSP/sanitização, não desligando WAF cegamente.
- Multi-tenant por conexão de banco em runtime (fonte de bugs) — aqui: **uma instância por cliente** (single-tenant físico), como no `cms/`.

### 1.5 `whm-simulator-docker/` — referência de infraestrutura
**Aproveitar a filosofia (sem emular o cPanel):**
- Container reproduzível e idempotente, `DocumentRoot=public/`, compressão gzip+brotli,
  OPcache de produção.
- `Makefile` idempotente (`make up`, `make reload`, `make check`), portas
  efêmeras e `COMPOSE_PROJECT_NAME` único por instância para isolamento multi-cliente.
- `.env` como fonte única, hostnames de serviços injetados no `.env`.
- Bind mount com `:Z` (SELinux), UID/GID alinhados com o host, logs em `docker logs`.

**Adaptar:** **não** emular EA4/Apache do cPanel (ADR-005); runtime PHP 8.4 próprio
(non-root), PostgreSQL 18 e Valkey em container, com o **Apache 2.4 do WHM como único
ponto de conexão**.

---

## 2. Pacote de ADRs (decisões a aprovar)

> **Canônico:** cada ADR foi **extraída** para `wiki/adr/ADR-0NN-*.md`
> (índice em [`wiki/adr/README.md`](../adr/README.md)), com contexto, justificativa
> (alternativas descartadas), consequências, **exemplo mínimo comparativo** e verificação.
> O texto abaixo é o **registro de origem** no plano; em caso de divergência, vale o arquivo
> em `wiki/adr/`.

### ADR-001 — Fundamentos: determinismo, padronização, nativo primeiro e simplicidade

**Fundamento (vale para todo o projeto):** o projeto deve ser **determinístico, previsível e
padronizado** — builds reprodutíveis (digests/lockfiles/vendorização), conteúdo público com corpo
determinístico (habilita `ETag`/`304`), **ordem explícita** em discovery/slots, clock e aleatoriedade
**injetados** (nada de `now()`/`rand()` soltos), dados de referência idempotentes e **testes sem
flaky**. O objetivo é ser **fácil de integrar, atualizar e materializar como código efetivo**,
fechando com **testes exaustivos finais**. Toda decisão/plano/teste reforça isso.

Antes de escrever qualquer classe nova, esgotar o que o Laravel 12/PHP 8.4 já oferecem
(FormRequest, Validation, Casts, Events, Jobs, Policies/Gate, Resources, Blade
Components, Flysystem, Cache, Scheduler). Regras de negócio em funções/Actions focadas e
**puras quando possível**, com dependências injetadas (DB, clock, id-gen, storage) por
parâmetro/clausura — nada de `new`/`resolve` no corpo. Preferir `pipe(...)` a loops mutáveis.

**Simplicidade acima de tudo:** **sem traits, sem classes abstratas, sem genéricos** e sem
hierarquias de OO. PHP concreto e funcional; classes só onde o framework exige (Provider,
Model, Middleware, Cast, Component). Autocontido e objetivo — **duplicação eventual é
preferível à abstração desnecessária**. O critério de qualidade é **funcionalidade e
efetividade**, não contagem de linhas. Toda ação emite log estruturado (`module`, `action`,
`id`, `user`, `ts`, `duration_ms`). Formatação: **Pint** (PHP) e **Prettier + ESLint**
(JS/TS), obrigatórios no `make check`/`make check` e no CI.

### ADR-002 — Mínimas dependências, vendorização e licenças permissivas
Antes de qualquer pacote, esgotar o nativo (ADR-001). **Zero CDN** — todo asset
(JS/CSS/fontes/ícones) é **vendorizado/buildado localmente** (Vite 8), com gate
`tools/vendor-check`. **Licenças permissivas apenas** (MIT/ISC/Apache-2.0/BSD/OFL),
mantendo `THIRD_PARTY_LICENSES`. Toda dependência é justificada pelo que resolve que o
nativo não resolve; priorizar extensões PHP/ferramentas do container a pacotes Composer.
Serviços de terceiros (WAF/CDN/analytics/tag manager) são **proibidos por padrão**
(ADR-020/ADR-019); exceção exige ADR própria.

### ADR-003 — Raiz = Laravel típico; `/src` = só domínio; `/wiki` = só documentação
A raiz de `public-v7/` é um **Laravel 12 típico e enxuto** (framework fora de `/src`).
Dentro de `/src` existem **somente** `core/`, `services/`, `layout/`, `modules/` e
`tests-e2e/`. O `DocumentRoot` é o `public/` padrão do Laravel (na raiz). Infra
(`docker/`, `docker-compose.yml`, `Makefile`) fica na raiz. Toda documentação vive em
`/wiki` (`plan/`, `adr/`, `specs/`, `ARQUITETURA.md`). O autoload mapeia as 4 camadas
para `/src` (`Core\`→`src/core`, etc.). O que não for necessário é removido do scaffold.

### ADR-004 — Grep-friendly: prefixo de domínio único em todos os artefatos
Toda feature/módulo tem um **token canônico** (kebab-case, ex.: `diario-oficial`) e **todos**
os artefatos derivam dele, em cada camada e estilo de caso. Uma única busca
(`grep -riE "diario[-_]?oficial"`) retorna a árvore completa — do banco à interface —
como no `prefeituras_v5/.agents/rules/domain_isolation.md`.

| Artefato | Estilo | Exemplo (`diario-oficial`) |
|---|---|---|
| Token canônico / log `module` | kebab-case | `diario-oficial` · `"module":"diario-oficial"` |
| Pasta/namespace do módulo | PascalCase | `src/modules/DiarioOficial/` · `Modules\DiarioOficial` |
| Model / Controller / Service / Action / DTO / Request / Resource / Policy | PascalCase | `DiarioOficial`, `DiarioOficialController`, `DiarioOficialService`, `DiarioOficialPublicarAction`, `DiarioOficialData`, `DiarioOficialRequest`, `DiarioOficialResource`, `DiarioOficialPolicy` |
| Eventos | PascalCase | `DiarioOficialPublicado` |
| Métodos, variáveis e funções helper | camelCase | `diarioOficialStore()`, `$diarioOficialRecords`, `fn diarioOficialClean()` |
| Views Blade | kebab-case | `diario-oficial-edit.blade.php`, `diario-oficial-show.blade.php` |
| Componentes Blade | kebab-case | `<x-diario-oficial.card>` → `diario-oficial-card.blade.php` |
| Rotas (nomes) | kebab/snake | `diario-oficial.index`, `portal.diario-oficial.show` |
| Migrations | snake_case | `2026_.._create_diario_oficial_tables.php` |
| Tabelas e colunas | snake_case | `diario_oficial_logs`, `diario_oficial_status` |
| Testes PHPUnit | PascalCase | `DiarioOficialPublicarActionTest` em `Test/` |
| Testes E2E | kebab-case | `diario-oficial-publicacao.spec.ts` + `histories/diario-oficial-publicacao.md` |
| Slots de layout | dot + kebab | `diario-oficial.after-main` |
| Permissões / feature flags | snake/kebab | `diario_oficial.publicar` |
| Assets e classes CSS próprios | kebab-case | `diario-oficial.js`, `.diario-oficial-highlight` |
| Config / env | snake_case | `diario_oficial_pdf_driver` |

**Lado público (Portal):** acrescenta o sufixo `Portal` para separar do administrativo —
`DiarioOficialPortalController`, `diario-oficial-portal-show.blade.php`, rota
`portal.diario-oficial.show`, contexto de log `[portal][diario-oficial]`.

**Exceções:** integração com o legado (ex.: `User` existente) deve ser documentada no código;
fora isso o prefixo é obrigatório.

**Abrangência:** vale para **todo** o projeto — `src/modules/*`, `src/services/*`,
`src/layout/*` e os componentes internos de `src/core/*` (ex.: `MediaOptimizer`,
`media-upload.blade.php`, `fn mediaVariantPath()`). Feature sem dono de módulo ainda
assim tem um token canônico (`observability-*`, `seo-*`).

**Enforcement:** `make check term=diario-oficial` deve retornar ≥1 arquivo em cada
camada esperada (Model/Action/View/Migration/Test); o gate `tools/name-check` valida que
todo arquivo dentro de `src/modules/{Modulo}` e `src/services/{Service}` carrega o token
canônico, e o CI falha sem ele.

### ADR-005 — Runtime em containers no layout padrão do WHM; Apache como proxy burro
O **layout do WHM permanece**: `git clone --depth=1` em `/home/<tenant>/public_html`, bind
montado nos containers. O **Apache 2.4 do WHM** é um **proxy burro** (`ProxyPass /.well-known !`
+ `ProxyPass / → web`/Caddy), mantendo certificados/AutoSSL/domínios. O container **ignora por
configuração** os arquivos do painel — `.user.ini` (`user_ini.filename=`), `php.ini`
(PHPRC/scan dir fixos), `.htaccess` (Caddy não lê; Apache não mapeia) e `error_log` (stderr +
deny no Caddy). `web`/`php-fpm` (non-root) são os mesmos em dev/prod; profiles
`debug`/`telemetry` são dev-only. Storage local configurável; um comando (`scripts/update_prod.sh`)
sobe/atualiza com otimizações. Specs 16/17.

### ADR-006 — Arquivos por bind mount local (layout padrão)
Repo montado como **`.:/var/www/html:Z`** direto de `/home/<tenant>/public_html` (dev: diretório
do programador). **Storage local e configurável**: padrão `public_html/storage`; `STORAGE_PATH`
permite apontar para disco fora da cota. Dados do PG em `DATA_PATH` (fora da cota). UID/GID
alinhados ao usuário non-root; OPcache recarregado por `scripts/update_prod.sh`/`make reload`.

### ADR-007 — PostgreSQL 18 em container, sem porta exposta
Com o banco **dentro de container** (ADR-005), ele deixa de depender do WHM: alvo é
`postgres:18-trixie` (pin por digest), **sem porta publicada**. Isso abandona as limitações do
PostgreSQL 10.23 do cPanel e habilita recursos modernos.

- PK `uuid` (**`uuidv7()` nativo** do PG 18); `timestamptz` para instantes; `numeric` para
dinheiro; `text` com `CHECK` (não `varchar(n)`); `NOT NULL` por padrão quando a ausência não é
significativa; **JSONB** para conteúdo.
- **Permitido agora:** `uuidv7()`, colunas geradas `GENERATED ... STORED`, `CREATE INDEX ... INCLUDE`,
  *skip scan*, **I/O assíncrono** (`io_method=worker`), `MERGE` (com parcimônia).
- **Continua valendo (segurança de dados, ADR-017):** `down()` só renomeia para `backup_*`;
  alteração de coluna em passos seguros; `CREATE INDEX CONCURRENTLY` **fora** de transação;
  papéis de banco com **privilégio mínimo**; auditoria imutável por trigger; WORM opcional.
- **Tuning** em `docker/postgres/postgresql.conf` (shared_buffers, work_mem, io_method=worker,
  `jit`, `pg_stat_statements`); índices validados com `EXPLAIN (ANALYZE, BUFFERS)`.

### ADR-008 — Otimização nativa obrigatória (optimize, OPcache e strict types)

O projeto entrega **sempre no modo otimizado**; otimização não é etapa posterior.

- **`php artisan optimize`** é passo obrigatório de build/deploy: congela config, rotas,
  views e eventos (`config:cache`, `route:cache`, `view:cache`, `event:cache`).
  `make optimize` no local; no deploy, logo após `migrate`.
  - **Pré-condições** (verificadas no CI): zero `env()` fora de `config/*.php`; zero
    closure em rotas; zero mutação de config em runtime. Como cada instância é
    **single-tenant**, não há troca de contexto que invalide o cache (a lição contrária
    do `tecvoto` não se aplica aqui).
  - Rollback usa `php artisan optimize:clear`.
- **Autoload de produção**: `composer install --no-dev --optimize-autoloader
  --classmap-authoritative`.
- **OPcache** ligado no container (plano 04): `opcache.enable=1`,
  `opcache.validate_timestamps=0` em prod, `opcache.max_accelerated_files` alto e
  `opcache.jit=tracing` quando estável. `make reload` limpa o cache após editar código.
  Preload opcional (`opcache.preload`) para o hot path.
- **`declare(strict_types=1)`** em **todo** arquivo PHP (inclusive migrations), forçado
  pelo Pint (`declare_strict_types`) e verificado no CI.
- **Eloquent Strict Mode** (`Model::shouldBeStrict()`): bloqueia lazy loading (N+1),
  descarte silencioso de atributos e acesso a atributo ausente.
- **`DB::prohibitDestructiveCommands()`** em produção (bloqueia `migrate:fresh`/`db:wipe`).
- **Assets (Vite 8)**: Rolldown/Oxc + Lightning CSS; bundle por área com code-splitting
  (`import()` por rota) e `manualChunks` de vendor; só `VITE_*` chega ao cliente (nunca
  segredo); assets com hash imutável, precompressão brotli/gzip e `base` correto.
- **Entrega**: compressão brotli/gzip, HTTP/2+ e cache imutável; headers/CSP/cookies/TLS
  ficam no ADR-020.
- **Evidência**: `php artisan about` mostra config/route/view/event cacheados; testes no CI
  garantem que `env()` não vaza para runtime e que todo PHP tem strict types.

### ADR-009 — UI: nativo primeiro + Tailwind 4 + daisyUI + Lucide; CMS próprio e independente

Usar Blade e roteamento/validação nativos como base. O **CMS é um shell próprio e
independente** em `src/layout/Cms` (namespace `Cms\`), inspirado no AdminLTE4/`sucesu-cms`,
com a filosofia de **“espaços, não controle”**: oferece seções e partials; cada módulo
implementa suas próprias views de CMS. **Sem Livewire** (Blade + formulários nativos + JS pequeno
vendorizado); **UI reativa exigiria nova ADR**. Filament: não.
Tailwind 4 (CSS-first) + daisyUI (primitivas acessíveis/tematizáveis) + Lucide (SVG
vendorizado, sem wrapper composer). **Zero CDN: tudo vendorizado/build local.** Justificativa:
controle de acessibilidade (eMAG), simplicidade, testabilidade e mínimo de dependências.

### ADR-010 — Web moderna: semântica, acessibilidade, CSS moderno, PWA e mínimo JS

Todo HTML prioriza os padrões modernos de semântica e acessibilidade; o JavaScript é a
última opção.

1. **HTML semântico primeiro.** Landmarks (`header/nav/main/aside/footer`), um `h1` por
   página, listas/tabelas/`button`/`a` corretos e elementos nativos (`<dialog>`,
   `<details>`, popover) antes de qualquer componente JS. ARIA só quando o nativo não
   basta (ARIA APG). Nunca "div-itis".
2. **Mínimo JS / progressive enhancement.** HTML+CSS resolvem navegação, layout, tema,
   filtros simples e estados. JS entra só no que não dá (editor, upload avançado,
   interação com servidor). Sem jQuery, sem SPA. Scripts `defer`. UI reativa (Livewire etc.)
   exigiria nova ADR — não é a base. Conteúdo público deve renderizar e navegar **com JS
   desabilitado**.
3. **Acessibilidade moderna (eMAG 3.1 + WCAG 2.2 AA).** Foco visível (`:focus-visible`),
   **alvo mínimo WCAG 2.2 ≥24×24px** (padrão da casa ≥44×44px em toque),
   `prefers-reduced-motion`/`prefers-color-scheme`/`prefers-contrast`/`forced-colors`,
   `inert` em áreas inativas, foco preso nativo em `<dialog>`, roving tabindex em widgets
   compostos, live regions com moderação, erros de formulário associados; VLibras e widget
   de acessibilidade. Critérios novos da WCAG 2.2: foco não ocluído, alternativas a
   arrastar, ajuda consistente, autenticação acessível.
4. **CSS moderno.** Tailwind 4 (CSS-first) + daisyUI; tokens em CSS vars; `oklch()` com
   fallback; Grid/Subgrid; **container queries** para componentes responsivos; `clamp()`
   para tipografia fluida (min/max em `rem`, nunca só `vw`); unidades modernas
   (`dvh`/`svh` no lugar de `100vh`, `cqi`/`cqw` de container, `ch`/`lh`);
   `:has()`/`:is()`/`:where()`/`light-dark()`; **`@layer`** para
   controlar a cascata (com `a11y` por último: `theme, base, components, utilities,
   overrides, a11y`) e **`@property`** para custom props tipadas/animáveis; propriedades
   lógicas (`margin-inline`, `padding-block`) para i18n; `content-visibility`/`contain`
   quando medido; View Transitions onde suportado e **degradável**. Sem `!important` e sem
   estilo inline.
5. **Responsividade real (mobile-first).** Layouts fluidos desde ~320px; imagens
   responsivas (`<picture>`, `srcset`, `sizes`, dimensões); tabelas com estratégia
   acessível; zero scroll horizontal; texto redimensionável até 200% sem perda
   (WCAG 1.4.4 — `clamp` com `rem`); matriz de viewports testada.
6. **PWA.** `manifest.webmanifest` + service worker (cache de assets/offline mínimo) +
   instalabilidade; ícones e `theme-color` do tema do tenant. O SW é *progressive
   enhancement*: a página funciona sem ele e o registro ocorre após `load`.
7. **Alinhamento contínuo à web moderna.** Baseline de browsers explícito; recursos novos
   atrás de `@supports`; **sem CDN** (tudo vendorizado/build local, com gate no CI); auditoria
   de HTML/CSS/JS no CI (axe + sniffCSS + orçamento).

**Enforcement:** gate do plano 11 (axe + eMAG + sniffCSS) e um teste e2e com
`javaScriptEnabled: false` nas páginas públicas; orçamento de JS/HTML no plano 14.

### ADR-011 — Tipografia de conteúdo: Atkinson Hyperlegible Next
A tipografia principal de **conteúdo** (corpo/editorial/`.prose`) tem como **default**
**Atkinson Hyperlegible Next** (Braille Institute), **self-hosted** como WOFF2 variable
(`font-display: swap`, preload, subset, fallback com `size-adjust`) e **ajustável pelo usuário**
na configuração global (token `--font-content`). Zero CDN; licença SIL OFL 1.1.

### ADR-012 — i18n multilíngue (pt_BR, en, es, fr) — nativo e JSONB
Locales `pt_BR` (default), `en`, `es`, `fr`. **Interface** nativa (`lang/{locale}/*.php` +
`__()`; namespace = token do módulo/service). **Conteúdo** traduzível em **JSONB keyed by
locale**, resolvido por **cast nativo** (`CastsAttributes`) com fallback — sem pacote nem trait.
Default no root e demais locales com prefixo (`/en/...`); `hreflang`; middleware de locale no Core;
`intl` para data/número.

### ADR-013 — DDD-lite de 4 camadas com fronteira física
- `src/core`: invisível ao usuário; auditoria, observabilidade, bus, permissões, descoberta, erros.
- `src/services`: headless, **agnóstico e sempre disponível** a qualquer módulo, com
  implementação padrão + componente de configuração/uso quando cabível (ADR-015).
- `src/layout`: estrutura fundamental de front-end, slots, presets, tema e componentes.
- `src/modules`: caso de uso autocontido — vive em `src/modules/{Grupo}/{Modulo}/` com um único
  `ModuleProvider.php` (ADR-014).
**Regra dura:** `modules/*` não importa `modules/*`; comunica-se por **eventos/ports do Core**.
`core` não importa `modules`. `services` não importa `modules`. Violação = build vermelho.
Vale também para **widgets/banners de outros módulos**: o módulo de origem registra um
provider no `WidgetRegistry` do Core e o perfil de destino só referencia o `id` — nunca import.

### ADR-014 — Autodescoberta de módulos e agrupamento por pasta
Módulos vivem em `src/modules/{Grupo}/{Modulo}/` e declaram tudo em **um único arquivo** na
raiz (`ModuleProvider.php`, com `register()` + `config(): array`). O Core varre `*/*`, instancia
o provider, **injeta `group` pelo nome da pasta** e carrega migrations/views/rotas por convenção
(sem `ServiceProvider` por módulo); gera `bootstrap/cache/modules_manifest.php` (zero DB no boot).
O CMS usa o manifesto para a sidebar **agrupada** e ordenada por `position`.

### ADR-015 — Services agnósticos, headless e sempre disponíveis
Services são capacidades transversais **agnósticas** (não conhecem módulos) e **headless** (sem
rotas/telas), **sempre disponíveis** a qualquer módulo de forma independente (não "sob demanda"),
com **implementação padrão** + **guia** e, quando cabível, um **componente de configuração/uso**
(Blade) que o módulo inclui. O módulo depende do **contrato no service**
(`src/services/{Nome}/Contracts`), nunca da implementação.
Referência: `sucesu-cms/V2/Services/SEO/`.

### ADR-016 — Tema e layout são dados, não código

Presets em Enum; **slots** registrados em runtime; configuração (ordem/visibilidade/conteúdo
de slots, tokens de tema) persistida em JSONB e editável pelo CMS. Trocar tema/layout = dado
+ CSS vars, sem deploy. Regras adicionais (plano 16):

- **Escopo com precedência determinística:** `default do tipo → global → preset → perfil de
  URL → instância`, e a **acessibilidade sempre por último** (vence toda a cadeia).
- **Slot = pilha ordenada:** empilha widgets em ordem com `gap` do design system; dois
  widgets à direita = posições 1 e 2. **Sem posição absoluta/pixel.**
- **Overrides locais = JSON de tokens** (`cor`, `borda`, `espacamento`, `variante`…),
  **nunca** CSS/HTML/classes (ADR-017).
- **Perfil por grupo de URL:** `LayoutProfile` (meta-serviço `services/Layout`) resolve
  `rota → perfil`; o módulo **só declara o grupo** (ex.: `/noticias`) e não tem layout próprio.
- **Dois escopos de tema:** `cms` e `portal` (cores/tipografia independentes).
- **`PresetSchema`** cobre paleta, tipografia, raio, sombra, espaçamento, densidade, movimento
  e variantes default de widget/banner; presets iniciais extraídos dos modelos 1..6.
- **Widget/Banner = tipo registrado** (schema de conteúdo, variantes de estilo e slots
  permitidos); conteúdo é **estrutura atômica (JSON)**, nunca HTML/CSS (ADR-017).
- **Widget de outro módulo = provider + placement:** o módulo de origem registra no
  `WidgetRegistry` do Core; o perfil de destino referencia o `id`. `layout` consome `core`;
  **nenhum** import entre módulos (ADR-013).
- **Conteúdo de outros módulos via porta do Core** (`WidgetSource`); nunca import direto
  entre módulos (ADR-013).

### ADR-017 — Migrations seguras e dados desacoplados da UI

Toda migration, **desde a primeira (Blueprint)**, segue a referência
`sucesu-cms/V2/Modules/Publicacoes/Noticias/Migrations/` — transação + `lock_timeout`,
log estruturado, `down()` que **nunca** apaga — e a **melhora** nos pontos em que o legado
se prendeu à apresentação.

1. **Contenção de perda de dados.** `up()`/`down()` em transação com
   `SET LOCAL lock_timeout = '3s'`; `down()` **nunca** usa `DROP`/`dropColumn` — renomeia
   para `backup_<tabela>_<timestamp>` (ou `backup_<tabela>_<coluna>_<timestamp>`). Exclusão
   real é etapa explícita de *contract*, após backup/WORM.
2. **Armazenar só o necessário.** Colunas existem para dado de domínio, nunca para
decoração; nada duplicado ou derivável; índices/constraints expressam a regra de negócio.
3. **JSONB quando cabível.** Conteúdo rico como AST (`corpo`), metadados extensíveis
   (`metadados`) e apresentação (`apresentacao`) em `jsonb` — a UI evolui sem migration.
4. **Nunca HTML nem estilo no banco.** Proibido gravar HTML renderizado, CSS, classes ou
   `style`. O editor grava **estrutura atômica**; a view/publicação renderiza.
5. **Apresentação fora dos dados.** "destacado", "ocupa 100% da largura", cor, ícone e
   ordem visual vivem no `services/Theme`/`SlotConfig` (JSONB) ou no Enum de preset —
   **nunca** como colunas booleanas presas a um layout (o erro das seeds antigas).
6. **Tipos e integridade.** PK `uuid` (**`uuidv7()` nativo**); `timestamptz`; FK com `onDelete`
   explícito; `softDeletes` em entidades editoriais; índices nomeados `tabela_coluna_idx`.
7. **Idempotência e guardas.** `Schema::hasTable`/`hasColumn` quando aplicável; tabela de
   chaves de importação com `key_hash` único (referência `v2_news_import_keys`).
8. **Busca nativa em PG 18.** Full-text com coluna `search_vector` `tsvector` por
   **coluna gerada** (`GENERATED ALWAYS AS (...) STORED`) + índice **GIN** — o PG 18 suporta
   colunas geradas (trigger só quando a expressão não couber numa coluna).
9. **Expand & contract.** Remoção de coluna/tabela em 2 releases: (a) adiciona o novo e
   migra dados; (b) depois renomeia o antigo para `backup_*`. Nunca no mesmo release.
10. **Seeds não definem o sistema.** Dados exigidos para funcionar (roles, permissões,
    categorias de sistema, presets, tokens default, flags) são criados de forma
    **idempotente pela própria migration/implementação** (`insertOrIgnore`/`upsert`/
    `firstOrCreate`), como no `seedDefaultCategories()` da referência. Seeders em
    `database/seeders` existem **apenas para demonstração**, nunca são pré-requisito de
    funcionamento, não rodam em produção e não carregam HTML/CSS/layout.
11. **Template único.** `declare(strict_types=1)`, `return new class extends Migration`,
    com helpers `safely()` (transação+lock+log) e `renameToBackup()`. Gate
    `tools/migration-check` no CI (proíbe `DROP`/`dropColumn` no `down()`, flag em
    `*_html`/`*_css`/`*_style` e em conteúdo rico fora de `jsonb`).

> Referência: `sucesu-cms/V2/Modules/Publicacoes/Noticias/Migrations/` (transação, log,
> `backup_*`, `metadata jsonb`, `search_vector` GIN, chaves de importação).
> Template completo e checklist em `wiki/specs/10-migracoes-e-dados.md` (plano 08) e
> tipos/tuning do banco em `wiki/specs/12-postgres-18.md`.

### ADR-018 — Dados Abertos como capacidade de primeira classe
Dataset é entidade de domínio (título, licença, periodicidade, recursos); metadados **DCAT/INDA**
+ `schema.org/Dataset`; export **CSV/JSON/XML** com URL estável, `ETag` e checksum; licença aberta
por padrão (**CC-BY 4.0**); catálogo `/dados-abertos` + API. Reaproveita `Search`/`Seo`/`Media`;
sem CKAN.

### ADR-019 — LGPD/privacidade como programa (não só criptografia)
Classificação de dados; base legal + ROPA; **direitos do titular (DSAR)**; retenção/descarte
agendado + `legal hold`; auditoria de acesso a dado pessoal; incidentes (ANPD); DPIA; encarregado
(DPO); consentimento e **analytics privacy-first** (sem Google Analytics). Nativo: cast
`encrypted` + `Audit` + Scheduler.

### ADR-020 — Segurança web moderna (defesa em profundidade)

- **Headers obrigatórios:** `Content-Security-Policy` **estrita hash-based e cacheável** (sem
  `unsafe-inline`; nonce por request só em página não-cacheável — ADR-022) + **Trusted Types** no
  CMS; `Strict-Transport-Security` (preload);
  `X-Content-Type-Options: nosniff`; `Referrer-Policy: strict-origin-when-cross-origin`;
  `Permissions-Policy` mínima; `Cross-Origin-Opener-Policy`/`Cross-Origin-Resource-Policy`.
- **TLS 1.3** obrigatório (container cPanel) e `URL::forceScheme('https')`.
- **Cookies:** `Secure; HttpOnly; SameSite=Lax` (prefixo `__Host-` na sessão),
  `session.use_strict_mode=1`; regeneração de id no login.
- **Auth:** `password_hash(..., PASSWORD_ARGON2ID)` + rehash; rate-limit no login;
  passkeys/WebAuthn como trilha opcional no admin.
- **Entrada/saída:** FormRequest/Validation; prepared statements; `htmlspecialchars(...)`;
  `json_encode(..., JSON_THROW_ON_ERROR)`; upload validado por MIME real + tamanho.
- **PHP/ini hardening:** `expose_php=Off`, `display_errors=Off` em prod,
  `allow_url_include=Off`, cookies endurecidos, `disable_functions` quando viável;
  proibido `rand()`/`mt_rand()`/`lcg_value()` para segredos (usar `Random\Randomizer`).
- **Cadeia de suprimentos:** lockfile commitado, `composer audit`/`npm audit` no CI, SBOM
  no build e segredos só por env/secret manager (nunca no repo).
- **Modelo de ameaça leve** (STRIDE) no início de cada módulo com PII/arquivos.

**Enforcement:** headers/CSP verificados no e2e (sniffCSS + asserts), `composer audit`
no CI e o checklist do `wiki/specs/05-seguranca-lgpd.md`.

### ADR-021 — Rate limiting e anti-abuso como hardening day-zero
Hardening em 3 camadas: **borda** (Apache `mod_reqtimeout`/`mod_qos`/`mod_security`, limites,
`TrustProxies`), **app** (`RateLimiter` nativo com store **Valkey**, `throttle:`), **formulários**
(honeypot, time-trap, limite por formulário+sessão; captcha só em último caso). `429` RFC 7807 +
`Retry-After`; estado no caminho quente sem banco; logs/métricas de `429`.

### ADR-022 — Cache de resposta, ETag e CSRF fora do HTML cacheável
`Cache-Control` público (com `stale-while-revalidate`) + **ETag/304** em GET de conteúdo;
**CSRF fora do HTML cacheável** (form pages `private`); **CSP sem inline** → política cacheável e
ETag estável (sem *static nonce*); assets `immutable` (Vite 8); **FPC opcional** em Valkey (sem
pacote); **sem minify de HTML em runtime**. Lições medidas no `v6-hardening`.

### ADR-023 — Qualidade no nível máximo, com ratchet
PHPStan + Larastan no **nível máximo** (`level: max`) com baseline commitado que **só encolhe**;
`phpstan-strict-rules`; PHPUnit estrito (`failOnWarning/Notice/Risky/Deprecation`); Pint; cobertura
por camada. Gate de fronteiras arquiteturais automatizado.

### ADR-024 — Observabilidade nativa: JSON + RFC 7807 (+ OTel opcional)
Todo log é JSON estruturado com `request_id`/`trace_id`, `module`, `action`, `user`, `duration_ms`.
Erros saem em RFC 7807. O **obrigatório é nativo** (Monolog JSON + `X-Request-ID`); **OTel é
trilha opcional** atrás de uma interface fina e do perfil `telemetry`, com export OTLP e
**fail-open** — o núcleo não depende dele para funcionar.

### ADR-025 — Acessibilidade/UX como gate de CI
Playwright + `@axe-core/playwright` (WCAG 2.2 AA + eMAG) e sniffCSS (CSS computado/contraste/foco)
são **gates** para templates-chave, não relatórios opcionais. Todo componente novo tem teste de a11y.

### ADR-026 — Media + Gallery: fluxo único de ingestão/digestão e `<picture>`
Dois services: **`Media`** (headless: `MediaInputer` porta única → otimizadores → variantes
**AVIF+WebP** em `[360,720,1200,1920,2560]`, `width/height` em `metadata` para CLS zero) e
**`Gallery`** (catálogo `gallery_media` com `variants`/`metadata` jsonb, `GalleryConfig`,
`gallery-picker`, arquivar/restaurar sem apagar o físico). Render por
**`<x-service-media::picture>`** (`<source>` AVIF/WebP + `<img>` fallback) — **sem reescrever HTML**.
Nativo: Flysystem + filas + jsonb; Imagick (libvips opcional).

### ADR-027 — SEO de ponta: service headless, JSON-LD `@graph` e arquivos de raiz
`services/Seo` headless + `metadata['seo']` (jsonb) + **`<x-service-seo::tags>`** (component-driven)
com **JSON-LD `@graph`** (`GovernmentOrganization`+`WebSite`/`SearchAction`+`GovernmentPage` com
sinal eMAG + schema do módulo: `NewsArticle`/`Legislation`/`GovernmentService`/`BreadcrumbList`/
**`Dataset`**). Arquivos de raiz dinâmicos (`robots.txt`/`sitemap.xml`/`security.txt`/manifest);
**404 real** + **301 redirects**; `hreflang`/canônica por locale; **analytics privacy-first**
(HyperLogLog, sem GA); indexação ativa nativa (IndexNow). **Sem trait** (ADR-001).

---

## 3. Entregáveis

- [x] `wiki/adr/ADR-001..031.md` (um por decisão, com contexto/justificativa/consequências/exemplo).
- [ ] Documento vivo `wiki/ARQUITETURA.md` (a visão de 1 página + diagrama).
- [ ] Este plano aprovado e marcado no `wiki/plan/00-index.md`.

## 4. Definition of Done

- [ ] ADRs 001–027 revisados e aprovados pelo mantenedor.
- [ ] Cada ADR cita referência de origem (projeto/arquivo) e o que foi descartado.
- [ ] Nenhuma decisão em aberto bloqueia os planos 02 e 03.

## 5. Riscos

| Risco | Mitigação |
|---|---|
| Repetir o excesso de complexidade do `cms/` | Núcleo mínimo; perfis opcionais; ADR-005 |
| Fidelidade cPanel consumir tempo | **Não aplicável**: runtime é independente (ADR-005); Apache do WHM é só o edge |
| Escolha de UI virar retrabalho | ADR-009 mantém o domínio desacoplado; CMS é shell próprio e independente |
| Fronteiras de módulo relaxarem com o tempo | Gate automático (plano 05) + revisão em PR |
