# Divergências, defeitos e lições (por leitura)

> **Limite do método.** O protocolo exige prova em runtime para severidade crítica (P3). O ambiente do
> `lotep` **não foi executado** nesta revisão: não há sonda, não há contagem antes/depois, não há
> workspace a restaurar. Portanto **nenhum defeito abaixo é “provado”**; todos são achados de leitura
> com âncora. Onde o efeito depende de configuração/framework, está marcado `[INFERENCE]`.

## 1. Premissas do pedido × código

| Premissa | O que o código mostra | Situação |
|---|---|---|
| “A V2 é a camada moderna do LOTEP” | Sim: `V2/` com PSR-4 próprio, `strict_types`, PHPStan 9, 650 testes, auto-descoberta | **confirmada** |
| “Muitas melhorias foram feitas lá para os mesmos módulos nominais” | Confirmado em Search, ExcelImporter, LandingSections, Formulário, Mídia/Galeria/SEO e no motor de permissões | **confirmada** |
| “A V2 usa UUIDv7” | O helper e a função de banco geram **v4** (`Core/Helpers/helpers.php:37-52`; `Core/Migrations/2026_06_01_000001...:20-41`). O helper `UUIDV7.php` existe, mas é usado só em auditoria/correlação | **divergente** |
| “PHPStan nível 9 garante qualidade” | Nível 9 **com ~60 `ignoreErrors`** amplos (`phpstan.neon:18-260`), incluindo `mixed` e acesso a propriedade indefinida | **divergente** |
| “Os testes cobrem os módulos” | 650 testes, mas `SupportConfigs` e `TransparenciaFiscal` não têm teste; RBAC não tem matriz rota×verbo | **divergente** |
| “O CMS é protegido por RBAC” | Existe RBAC, mas o middleware é **fail-open** fora de `active_pattern` e deixa `GET/HEAD` passarem | **divergente** |

## 2. Defeitos por leitura (potenciais)

| # | Severidade | Defeito | Evidência |
|---|---|---|---|
| D1 | **Alta** | `BlockRenderer` compila/executa Blade vindo do banco | `V2/Modules/Support/StructuralLandingEditor/Services/BlockRenderer.php:143-230` |
| D2 | **Alta** | Middleware de permissão é fail-open fora de módulo e ignora o parâmetro da rota | `V2/Core/Middleware/V2PermissionMiddleware.php:40-48,75-76` |
| D3 | **Alta** | HTML livre renderizado (`contentRichHtmlFree`, `BasicPage.runtime`, blocos do portal) | `BasicPage/Layout/Views/portal/runtime.blade.php:6`; `layouts/portal/base.blade.php:99-121` |
| D4 | **Alta** | SVG aceito sem sanitização no `MediaInputer` | `V2/Services/Media/Services/MediaInputer.php` (allowlist `image/*`) |
| D5 | **Média** | Login sem throttle; logout por `GET` | `V2/Core/Routes/cms.php:13-16` |
| D6 | **Média** | CSRF isento por `cms_ai_token_valid` e em `cms/v2/gallery/*` | `app/Http/Middleware/VerifyCsrfToken.php:16-28` |
| D7 | **Média** | Bypass de autorização por e-mail fixo e `super-admin` | `PermissionService.php:73`; `helpers.php:24-27` |
| D8 | **Média** | Portal da Ouvidoria aceita `status/protocol/is_anonymous` do request | `Servicos/Ouvidoria/Controllers/Portal/OmbudsmanPortalController.php:32-35` |
| D9 | **Média** | Notícias: `update()` publica sem aprovação; aprovar/rejeitar não limpam cache | `Publicacoes/Noticias/Controllers/NoticiasCmsController.php:378-417,592-617` |
| D10 | **Média** | `MailService` grava corpo completo (PII) e troca o mailer global | `V2/Services/MailService/MailService.php` |
| D11 | **Média** | `StorageService` degrada S3→local em silêncio | `V2/Services/Storage/StorageService.php` |
| D12 | **Média** | Webhook sem nonce/timestamp (replay), O(N) tokens, sem rate limit | `V2/Services/Webhook/Middleware/WebhookTokenMiddleware.php` |
| D13 | **Baixa** | `generateSlug` de Licitações usa `static::$oldSlug` nunca definido | `Publicacoes/Licitacoes/Models/Licitacao.php:143-165` |
| D14 | **Baixa** | `scopeSearch` da Ouvidoria com `orWhere` no topo quebra filtros | `Servicos/Ouvidoria/Models/OmbudsmanRequest.php:153` |
| D15 | **Baixa** | Transparência carrega filhos sem `active()` (N+1 + inativos) | `Portal/TransparencyPortalController.php:17-19` |
| D16 | **Baixa** | `shell_exec`/`which tail` no leitor de log do suporte | `SupportConfigs/Services/SystemDiagnosticsService.php:205-208` |
| D17 | **Baixa** | `Redis::keys` para limpar cache de páginas (O(N)) | `NoticiasCmsController.php:1022-1040` |
| D18 | **Baixa** | `result_token` prometido como assinado, mas não é | `V2/Services/FormularioDinamico` |
| D19 | **Baixa** | Reindex referencia módulos inexistentes e os ignora | `V2/Services/Search/Console/Commands/ReindexPublicSearch.php` |
| D20 | **Baixa** | `entity_id uuid` no índice impede módulos com id inteiro | `V2/Services/Search/Services/PublicIndexerService.php:22-63` |

## 3. Padrões que o V2 acerta (não são defeito)

1. **`strict_types` em 100%** e alvo de sintaxe declarado (`LINT_BASELINE.md:14`).
2. **Baseline como contrato**: contagens, comandos e “skip não é passe” (`TEST_BASELINE.md:9-22`).
3. **Migrações reversíveis** com `lock_timeout` e `down()` que **renomeia** para `backup_*`
   (`Core/Migrations/2026_06_01_000005...:63-101`) — mesma política da ADR-017.
4. **UI padronizada com matriz de conformidade** e lista de lacunas (`CMS_UI_STANDARD.md:79-122`).
5. **Preparação de mídia em lote** antes do render (`prepareSections` + um `whereIn`) — anti-N+1.
6. **Índice de busca desnormalizado** com trigger e upsert idempotente.
7. **Pipeline de importação com preview + log + streaming**.
8. **Formulário versionado com scoring e consentimento** (LGPD no desenho).
9. **Primitivas de segurança pontuais** (`safe_previous`, sanitizador de snippet, magic bytes).

## 4. Lições para o protocolo de dossiê

1. **Projeto fora do workspace.** O `lotep/` vive ao lado do repo de dossiês. Foi preciso acrescentar
   `raiz_codigo` ao `projetos.json` (resolução de caminho citado e das pastas de módulo) — antes, o
   auditor só resolvia relativo à raiz do workspace. Regra: *todo gate resolve caminho na raiz do
   código do projeto, não na raiz do dossiê*.
2. **Módulos aninhados.** O modelo `pastas` assumia um nível (`Modules/<Mod>`); o `lotep` usa dois
   (`Modules/<Grupo>/<Modulo>`). Foi acrescentado `modulos.profundidade: 2` com slug `grupo/modulo`.
   Regra: *o nível de aninhamento é dado do projeto, não do protocolo*.
3. **Módulos fora da pasta de módulos.** 15 serviços em `V2/Services/` são capacidades de negócio
   (busca, importação, formulários) mas não são “módulos” no inventário. O protocolo precisa de um
   conceito explícito de **capacidade transversal**; hoje elas ficam só no panorama e no
   `cobertura.lacunas` quando o estado vira `completo`.
4. **Serviço com dois caminhos.** O módulo de Formulários Dinâmicos redireciona para um motor mais
   novo e mantém um caminho morto de gravação. O dossiê precisa declarar **qual caminho é o vigente**;
   documentar os dois sem hierarquia induz a reconstrução errada.
5. **Número de teste não é cobertura.** “650 testes, 0 falhas” convive com módulos sem nenhum teste.
   O gate do protocolo não mede cobertura; um projeto só deve declarar “testado” por módulo.
6. **`estado: preparado` é legítimo.** Esta revisão entrega panorama + oito dossiês e declara os
   demais como destino no inventário; o gate de cobertura só exige 100% quando o estado é `completo`.
   Isso evita dossiê raso só para “fechar número”.

## 5. O que ficou fora desta rodada

- **F5 em runtime** (subir o ambiente, sondar, reproduzir D1–D20) — exige banco, credenciais e um
  workspace descartável; não foi feito.
- **Dossiês** de AgendaEventos, AInstituicao, EmpresasAssociadas, Faq, Glossario, PaginasCustomizadas,
  PerguntasFrequentes, Webhook, MigracaoLegada e dos 15 serviços (cobertos no panorama, não no
  contrato de 9 arquivos).
- **Comparação com o corpus V5/V6** (`dossie_modules/<slug>/`): feita apenas no que o panorama cita;
  o confronto módulo a módulo fica para quando o aproveitamento for priorizado.
