# QA de revisão — Onda 2 (gate G2)

**Data/snapshot:** 2026-10-21 00:20 (-03). Documentos e `sucesu-cms/V2/` lidos nesse estado;
`git -C sucesu-cms status --porcelain V2` vazio.

**Escopo:** amostra de 13 documentos de serviço (≈59% dos 22 documentos da Onda 2, acima
dos ~25% exigidos), com leitura integral, confronto de citações/trechos/API/rotas/schema e
verificação cruzada entre serviços dependentes.

**Fora de escopo:** editar documentos de unidade ou código, executar migrations/testes,
validar o ambiente de produção (S3, proxy/WAF, PostgreSQL real).

**Método:**

1. Leitura integral dos 13 arquivos da amostra e das seções correspondentes de
   `PLANO.md`, `_contrato/TAXONOMIA.md`, `TEMPLATE_SERVICO.md` e
   `CHECKLIST_EXTRACAO.md`.
2. Validador de citações: extração de todas as ocorrências `caminho:linha[-linha]`,
   resolução do caminho em `sucesu-cms/` e comparação com o total de linhas do arquivo
   (915 ocorrências, 0 caminhos inexistentes, 24 faixas fora do arquivo).
3. Conferência de trechos: comparação do bloco com o intervalo citado no código, quando o
   bloco se declara literal (diferenças de indentação/condensação não contam como erro;
   `…`/`// ...` foram respeitados).
4. Verificação semântica dos pontos de maior impacto: escolha de adaptador de storage,
   índices de busca e triggers, compositor de SEO, assinatura PKCS#7, fluxo SMTP,
   registry de widgets, ciclo do Gallery, dashboard.
5. Conferência dos índices parciais contra o texto e o código (rotas, tabelas, componentes,
   serviços) e execução das ferramentas de gate.

## 1. Amostra

| Documento | Unidade | Linhas | Papel na amostra |
|---|---|---:|---|
| `03_servicos-ui/content-rich-editor/spec.md` | SVC-CONTENTRICHEDITOR | 998 | núcleo + formato AST |
| `03_servicos-ui/content-rich-editor/ast-e-renderers.md` | SVC-CONTENTRICHEDITOR | 515 | contrato de nós/marks |
| `03_servicos-ui/landing-sections-editor/spec.md` | SVC-LANDINGSECTIONSEDITOR | 632 | registry + preview + mídia |
| `03_servicos-ui/landing-sections-editor/widgets-e-schemas.md` | SVC-LANDINGSECTIONSEDITOR | 573 | catálogo de widgets/schemas |
| `03_servicos-ui/gallery/spec.md` | SVC-GALLERY | 912 | núcleo + segurança + consumidores |
| `03_servicos-ui/gallery/ui-contrato.md` | SVC-GALLERY | 624 | DOM/eventos/JS do picker |
| `02_servicos-base/storage.md` | SVC-STORAGE | 508 | adaptadores local/S3 |
| `03_servicos-ui/seo/spec.md` | SVC-SEO | 689 | head/robots/sitemap/telemetria |
| `02_servicos-base/digital-signature/spec.md` | SVC-DIGITALSIGNATURE | 737 | certificado, cofre e PDF |
| `02_servicos-base/mail-service.md` | SVC-MAILSERVICE | 628 | SMTP, templates e log |
| `03_servicos-ui/search/indexacao.md` | SVC-SEARCH | 295 | escrita do índice por entidade |
| `02_servicos-base/dashboard.md` | SVC-DASHBOARD | 502 | agregação e custo |
| `03_servicos-ui/search/spec.md` | SVC-SEARCH/SVC-INDEX | 588 | motor de busca e README |

Total da amostra: 7.613 linhas. A Onda 2 tem 13 unidades de serviço (STORAGE, MEDIA,
ACTIVITYLOGGER, MAILSERVICE, DIGITALSIGNATURE, FEEDRSS, DASHBOARD, CONTENTRICHEDITOR,
LANDINGSECTIONSEDITOR, GALLERY, SEO, SEARCH, EXCELIMPORTER); as 13 têm documento.

## 2. Verificações do gate

| # | Critério | Resultado |
|---|---|---|
| 1 | Toda afirmação técnica com `caminho:linha` que confere | **Ressalva.** 0 caminhos inexistentes em 915 citações; 24 faixas passam do fim do arquivo e há deslocamentos de 1 linha em tabelas de rotas (G2-08). Nenhuma citação aponta para arquivo errado. |
| 2 | Quatro dimensões com substância | **OK.** Todos os 13 documentos têm Efeito, Abordagem, Particularidades e Integrabilidade com dados concretos (tabelas de efeito, mecânica, RN-*, API/consumidores). Não há ficha vazia nem “ver código”. |
| 3 | 3–8 trechos pivô, ≤20 linhas, explicados | **OK com ressalva.** Os trechos pivô são 4–7 por documento e cada um tem explicação de efeito. Um bloco do MailService tem 24 linhas (condensado); o exemplo JSON de AST do CRE tem 54 linhas, mas é formato de dados, não trecho pivô. |
| 4 | Receitas compilam e há anti-uso | **OK.** 2–5 receitas por documento; nomes de classes/métodos/rotas conferem (Storage, Search, Mail, Signature, SEO, CRE, LSE, Gallery). Há 1–5 anti-usos por documento, todos com consequência explícita. |
| 5 | Contratos entre serviços | **OK em geral.** CRE⇄LSE (`assets_only`, 11 renders), CRE⇄Gallery (evento `lotep:open-gallery-picker-global-media-picker`), Gallery⇄Media/Storage (`owner_module`, `variants`), Search⇄SEO/Dashboard (`v2_public_search_index`, `getPageRanking`), Mail⇄FaleConosco/AssocieSe. Duas inconsistências de direção/inventário em G2-10. |
| 6 | Índices coerentes com o texto | **Ressalva.** Rotas, tabelas e componentes conferem em contagem e conteúdo; `R-GALLERY-05` repete o 404 incorreto (G2-01) e `servicos.csv` canônico não existe (G2-09). |
| 7 | Erros materiais/omissões perigosas | **4 achados P2** (G2-01 a G2-04), 4 P3 e 1 bloqueio de índice. |

Ferramentas executadas no snapshot:

- `python3 tools/lint_docs.py` nos 12 documentos com front-matter: 0 erros.
- `python3 tools/coverage.py --docs`: 38/56 unidades com documento; todas as 13 unidades de
  serviço da Onda 2 estão cobertas.
- `python3 tools/coverage.py --strict`: integridade do manifest OK (930 arquivos/56 unidades).
- `python3 tools/merge_indices.py --check`: “Índices OK” (valida as parciais, não cria os
  canônicos).
- `git -C sucesu-cms status --porcelain V2`: vazio.

## 3. Achados

### G2-01 — Download da Gallery é documentado como 404, mas responde 500

**Severidade: P2.**

`gallery/spec.md:94-95` afirma `404 "Mídia não encontrada ou acesso negado."` e
`404 "Arquivo físico não encontrado."`; `_indices/parcial-SVC-GALLERY/rotas.csv:6` repete
“404 para mídia/arquivo ausente”. No código, as duas checagens chamam `abort(404, ...)`
**dentro** do `try` de `GalleryController@download`
(`V2/Services/Gallery/Controllers/GalleryController.php:248-250,254-256`), e o
`catch (\Exception $e)` do mesmo método (`:310-313`) captura a `NotFoundHttpException`
(que estende `HttpException`, que estende `\RuntimeException`) e devolve
`500 {"success":false,"message":"<mensagem do abort>"}`.

**Impacto:** um teste de paridade escrito a partir do documento espera 404 e falha; o
consumidor do endpoint não recebe nem o status 404 nem um corpo vazio. A mensagem continua
correta, mas o contrato HTTP documentado não é o real.

**Correção recomendada:** documentar o comportamento real nas duas linhas (`500` + JSON) e
registrar a causa (`abort` capturado pelo `catch`); se o código for corrigido no futuro,
mover as checagens para fora do `try` ou re-lançar `HttpException`.

### G2-02 — Ranking SEO não é reproduzível: contagens erradas e listas omitidas

**Severidade: P2.**

`dashboard.md:55` e `:405-406` afirmam “16 `NOT LIKE` de prefixo” e “~43 de extensão”;
`seo/spec.md:325` e `:527` afirmam “~47 predicados”. O código tem **17 prefixos**
(`SEOService.php:203-207`, cada um com `not like prefixo/%` **e** `!= prefixo`, portanto 34
condições) e **35 extensões** (`:209-216`), totalizando **69 predicados**
(`:220-227`). Nenhum dos dois documentos enumera os valores excluídos; `PC-SEO-12` só diz
“exclui prefixos/extensões”.

**Impacto:** a reimplementação cega não consegue reproduzir `getPageRanking()` — e o
ranking alimenta o card/tabela/treemap do dashboard. Um teste baseado nos números
documentados (16/43 ou 47) não fecha, e a ausência da lista impede verificar quais URLs
ficam fora.

**Correção recomendada:** corrigir as contagens e transcrever as duas listas no `seo/spec.md`
(por exemplo, tabela “prefixos excluídos” e “extensões excluídas”, citando
`SEOService.php:203-216`); atualizar `PC-SEO-12` para citar a lista, não só o comportamento.

### G2-03 — Trecho do widget de depoimento cita linhas inexistentes

**Severidade: P2.**

`widgets-e-schemas.md:186` e `:379` citam
`V2/Modules/Institucional/SobreNos/LseWidgets/Widgets/SobreNosTestimonialWidget.php:70-86`
(e `:70-85`). O arquivo tem **68 linhas**; o método `getAttachmentIds()` está em
**26-42** e `prepare()` em **49-67**. O bloco é correto, mas a citação aponta para o fim do
arquivo (e para uma linha que não existe), justamente no exemplo canônico de contrato de
mídia.

**Impacto:** quem segue a citação não encontra o método; a “receita de extensão” perde
verificabilidade.

**Correção recomendada:** trocar as duas citações por `:26-42` (e, se o exemplo incluir
`prepare`, `:49-67`), mantendo a explicação de `caminho => id`.

### G2-04 — `PC-LSE-16` afirma 21 slugs, mas `getSectionTypes()` devolve também os de módulo

**Severidade: P2.**

`widgets-e-schemas.md:552` (`PC-LSE-16`) exige “`getSectionTypes()` devolve exatamente 21
slugs, na ordem de `$legacyConfigs`”. O método devolve `WidgetRegistry::all()`
(`LandingSectionsEditorRendererService.php:81-85`), e `registerLegacyWidgets()` só adiciona
21 tipos **se o slug não existir** (`:38-73`). No boot normal, `Homepage/ModuleProvider.php:22-31`
registra 10 `sucesu-*` e `SobreNos/ModuleProvider.php:20-24` registra 5 `sobre-nos-*`:
o resultado é **36 slugs** (15 de módulo + 21 padrão). O próprio documento diz em §8.3 que
os tipos de módulo vêm antes dos padrões.

**Impacto:** é um critério de aceite falso. Uma reimplementação que retorne apenas 21 oculta
os 15 widgets de módulo do dropdown/preview; um teste escrito a partir de `PC-LSE-16` falha
no ambiente real.

**Correção recomendada:** reescrever `PC-LSE-16` como “21 padrões + 15 de módulo no boot
normal (36 no total); padrões na ordem de `$legacyConfigs`, módulos na ordem de registro”,
mantendo a separação por prefixo no dropdown.

### G2-05 — Dashboard declara 9 `COUNT`, mas executa 10

**Severidade: P3.**

A tabela de métricas lista 2 (Notícias) + 1 (Eventos) + 2 (Mensagens) + 1 (Vagas) +
1 (Candidaturas) + 1 (Leads) + 1 (Parceiros) + 1 (Índice) = **10 `COUNT`**
(`DashboardStatsService.php:61-62,80,98-99,117,135,153,171,187`). `dashboard.md:49` diz
“Total dos cards: **9 `COUNT`**”; `:58` repete “~9 `COUNT`”; `:247` diz “8 blocos, 9
`COUNT`”.

**Impacto:** custo e checklist de performance ficam subestimados em 1 consulta; o número
contradiz a própria tabela do documento.

**Correção recomendada:** trocar 9 por 10 nas três ocorrências (o “8 blocos” está correto).

### G2-06 — Exceção errada para rota ausente no dashboard

**Severidade: P3.**

`dashboard.md:68,227,287,321,454` afirmam que `route()` lança `UrlGenerationException` quando
a rota não existe. No Laravel 5.5, `UrlGenerator::route()` lança
`InvalidArgumentException("Route [{$name}] not defined.")`
(`vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php:299-306`);
`UrlGenerationException` é usada para parâmetros faltantes em rota existente. O efeito
(500 não capturado) continua correto.

**Impacto:** um `catch` escrito para a classe errada não captura a falha na reimplementação.

**Correção recomendada:** trocar `UrlGenerationException` por `InvalidArgumentException`
(ou “`InvalidArgumentException` de rota não definida”) nas cinco ocorrências.

### G2-07 — Consumidor inexistente no front-matter do SEO

**Severidade: P3.**

`seo/spec.md:10` lista `MOD-SUPPORT-DASHBOARD` em `consumidores`. O dashboard é a unidade
`SVC-DASHBOARD` (`02_servicos-base/dashboard.md:2,6`; manifest com 56 unidades); não existe
`MOD-SUPPORT-DASHBOARD`. O texto do documento usa corretamente “dashboard”; só o
front-matter está errado.

**Impacto:** o índice de consumidores aponta para uma unidade inexistente; ferramentas que
resolvem IDs (coverage/merge) não conseguem ligar a dependência.

**Correção recomendada:** trocar por `SVC-DASHBOARD` no front-matter.

### G2-08 — Faixas de citação fora do arquivo ou deslocadas em 1 linha

**Severidade: P3.**

O validador encontrou **24 faixas** que terminam depois da última linha do arquivo (0
caminhos inexistentes). Exemplos:

| Documento | Citação | Realidade |
|---|---|---|
| `content-rich-editor/spec.md:870-878` | `Assets/README.md:1-45`, `Routes/README.md:1-25`, `Views/README.md:1-35`, `Renderers/README.md:1-15`, `LIVE_PREVIEW_IMPLEMENTATION.md:1-120`, `NodeRendererInterface.php:1-12`, `MarkRendererInterface.php:1-12`, `RendererStrategyInterface.php:1-19`, `CmsAuthenticate.php:1-50`, `CspMiddleware.php:1-120` | 42, 20, 27, 13, 116, 9, 9, 18, 47 e 111 linhas |
| `seo/spec.md:66,607,644` | `MaintenanceMiddleware.php:40-56`; `ClearPageCache.php:21-49` | 52 e 47 linhas |
| `mail-service.md:335,621,626` | `FaleConoscoPortalController.php:169-172` e `:106-172`; `V2PermissionMiddleware.php:24-150` | 166 e 138 linhas |
| `gallery/spec.md:894` | `MediaInputer.php:100-290` | 276 linhas |
| `widgets-e-schemas.md:186,379` | `SobreNosTestimonialWidget.php:70-86` / `:70-85` | 68 linhas (método em 26-42; ver G2-03) |

Além disso, as tabelas de rotas de CRE e LSE estão deslocadas em 1 linha:
`content-rich-editor/spec.md:628-629` cita `Routes/cms.php:8`/`:10`, mas o grupo e o upload
estão em `:9`/`:11`; `landing-sections-editor/spec.md:471-478` cita `:11-22`, mas as rotas
estão em `:12-23`.

**Impacto:** a citação é o mecanismo de verificação do corpus; faixas que não existem
obrigam o leitor a “corrigir de cabeça” e enfraquecem o gate de evidência. Não altera
comportamento documentado.

**Correção recomendada:** corrigir as faixas e, de preferência, estender
`tools/lint_docs.py` para resolver o caminho e validar `linha_final <= total_linhas` (e,
quando o bloco tiver cabeçalho, comparar o início do trecho com a linha citada).

### G2-09 — Gate: `servicos.csv` canônico ausente e 14 linhas nas parciais

**Severidade: bloqueante de gate (G2).**

`_indices/` contém apenas diretórios `parcial-*`; não existe `_indices/servicos.csv` (nem
`rotas.csv`, `tabelas.csv`, `componentes.csv` canônicos). `merge_indices.py --check` valida
as parciais e imprime “Índices OK”, mas **não grava** os canônicos. As parciais somam **14
linhas de dados** em `servicos.csv`, incluindo uma linha `SVC-INDEX` — unidade que é apenas
o `V2/Services/README.md`, sem API de serviço. O G2 exige “13 serviços ... `servicos.csv`
com 13 linhas”.

**Impacto:** o gate G2 não pode ser declarado fechado; os gates seguintes (G3/G4) dependem
dos canônicos consolidados.

**Correção recomendada:** rodar `python3 tools/merge_indices.py` (sem `--check`) para emitir
os canônicos; decidir se `SVC-INDEX` permanece em `servicos.csv` (recomendação: remover a
linha, pois não é serviço, deixando 13) e repetir `--check`. Registrar a consolidação no
`README.md`.

### G2-10 — Inconsistências menores de direção/inventário

**Severidade: P3.**

1. `widgets-e-schemas.md:537` põe `SVC-CONTENTRICHEDITOR` na coluna “Consumidor” do catálogo
   LSE. A dependência é a inversa: `SVC-LANDINGSECTIONSEDITOR` declara
   `depende_de: [..., SVC-CONTENTRICHEDITOR, ...]` (`landing-sections-editor/spec.md:7`).
   A descrição (“campo rico nos forms ... `toHtml` nos renders”) descreve corretamente o
   uso, mas o rótulo induz a ordem de construção errada. Trocar para “dependência” ou
   remover da tabela de consumidores.
2. `content-rich-editor/spec.md:656` lista `BasicPage` como “Consumidor” na tabela de
   persistência, enquanto `:826` afirma “**não consome**” (CodeMirror, `content_type`
   htmlmixed/css/javascript). A própria tabela ressalva “(HTML/CSS/JS, **não** AST)”; o
   rótulo deveria ser “não consumidor / coluna homônima” para não contradizer §9.
3. `gallery/spec.md:843` fala em “17 consumidores”, mas a tabela §9 tem 18 linhas
   (incluindo “Seeds/deploy”). Ajustar a contagem ou excluir explicitamente seeds da conta.

## 4. Correções recomendadas por documento

| Documento | Correções |
|---|---|
| `03_servicos-ui/gallery/spec.md` | G2-01 (2 linhas da tabela de erros; ver índice); G2-10.3 (contagem). |
| `_indices/parcial-SVC-GALLERY/rotas.csv` | G2-01: `R-GALLERY-05` → 500 JSON para mídia/arquivo ausente. |
| `02_servicos-base/dashboard.md` | G2-02 (contagens/listas), G2-05 (9→10), G2-06 (`InvalidArgumentException`). |
| `03_servicos-ui/seo/spec.md` | G2-02 (listas e contagens), G2-07 (`SVC-DASHBOARD`), G2-08 (faixas). |
| `03_servicos-ui/landing-sections-editor/widgets-e-schemas.md` | G2-03 (citações 26-42), G2-04 (`PC-LSE-16`), G2-10.1 (direção do consumo). |
| `03_servicos-ui/landing-sections-editor/spec.md` | G2-08 (rotas 12-23); sem erro semântico encontrado. |
| `03_servicos-ui/content-rich-editor/spec.md` | G2-08 (faixas 870-878 e rotas 9/11); G2-10.2 (BasicPage). |
| `02_servicos-base/mail-service.md` | G2-08 (faixas FaleConosco/V2PermissionMiddleware); conteúdo verificado. |
| `03_servicos-ui/content-rich-editor/ast-e-renderers.md` | G2-08 (faixas de interfaces). |
| `02_servicos-base/storage.md` | Nenhuma correção obrigatória; citações e comportamento conferem. |
| `02_servicos-base/digital-signature/spec.md` | Nenhuma correção obrigatória; citações/claims conferem. |
| `03_servicos-ui/search/spec.md` e `search/indexacao.md` | Nenhuma correção obrigatória; o bug do `CMSIndexerService::remove` e o envelope AJAX estão corretos. |
| `_indices/` (coordenação) | G2-09: gerar canônicos, resolver `SVC-INDEX` em `servicos.csv`, repetir `merge_indices.py --check`. |

## 5. O que foi conferido sem achado (amostra de profundidade)

Para registrar que a revisão não se limitou à forma:

- **Storage:** `resolveAdapter()` escolhe `S3Adapter` só com `media_disk='s3'` e três
  credenciais; sem credenciais usa `LocalAdapter('s3')` (`StorageService.php:33-57`);
  `S3Adapter::put` cai para `public` mas `getDisk()`/`url()` continuam `s3`
  (`S3Adapter.php:22-37`); `LocalAdapter::url` colapsa barras e usa `filesystems.disks.*.url`
  (`LocalAdapter.php:85-96`); `UploadedFile` vira conteúdo no local, mas não no S3
  (`LocalAdapter.php:32-44`); `/tmp/lotep_s3_downloads` achata `/` em `_`
  (`S3Adapter.php:157-185`); `Intervention\Image` não está em `composer.json`/`vendor`
  (`UploadService.php:7,63-93`). Tudo confere.
- **Search:** UPSERT por `(entity_type, entity_id)` e `(module, edit_url)`; trigger de
  `search_vector` com pesos A/B/C; `BoardMember::deleted` remove CMS por `id` em vez de
  `edit_url`; `ReindexPublicSearch` cobre só 4 grupos e ignora `--only` inválido;
  `publicResultUrl` é um `switch` fechado; JS do spotlight lê `data.results` enquanto o
  controller devolve `data.data.results`. Tudo confere.
- **SEO:** composer em 8 views, 8 `trackAccess()` por página padrão; `<title>` não recebe
  sufixo quando `$seoTitle` existe; `bindModel` só sobrescreve campos não vazios; seed
  reexecutado apaga valores (`updateOrInsert`); sitemap de eventos não filtra
  `published_at`; `robots.txt` falha 500 sem a rota de Notícias. Tudo confere.
- **DigitalSignature:** derivação `sha256(APP_KEY|v2-digital-signature|userId)`, AES-256-CBC,
  IV aleatório; bypass de validade por e-mail de suporte; `extracerts` descartado; TCPDF
  recebe `setSignatureAppearance(page,x,y,w,h)` enquanto a assinatura esperada é
  `(x,y,w,h,page,name)`; `signPdf` só captura `SignatureException` e loga falha depois de
  assinar. Tudo confere.
- **MailService:** falha de SMTP nunca lança (vira `MailResult(false)`); `driver` é ignorado
  e todo envio vira `Swift_SmtpTransport`; `from` é omitido sem `from_address`; corpo final
  vai para `v2_mail_logs`; API não expõe cc/bcc/anexos; limite de histórico 200. Tudo confere.
- **CRE/LSE:** traversal bottom-up, marks na ordem do array, nó sem renderer transparente,
  `htmlspecialchars` em texto, `contentRichHtmlFree` cru, `tableWidth` cru, `assets_only`
  mantém modais/CSS/JS, registry LSE “último vence”, padrões registrados só se ausentes,
  `sanitizeContent` remove tags no preview, `getAttachmentIds` retorna `caminho => id`,
  `prepareSections` faz 1 `whereIn`. Tudo confere.
- **Gallery:** 7 rotas sem `CmsAuthenticate`, CSRF isento em subcaminhos, escopo por
  `owner_module` vindo do request, `find()` com `includeArchived`, `variants` string no
  upload e array nas leituras, `delete` só do dono, picker single-select com guard global
  de scripts. Tudo confere, exceto o status de download (G2-01).

## 6. Lacunas da revisão

- Não foi possível reproduzir `getPageRanking()` contra uma base real nem validar a ordem
  exata de `File::directories()` em produção; ambos já estão registrados como `[L]`/`LG-*`
  nos documentos de origem.
- Não foram revisados os documentos de MEDIA, ACTIVITYLOGGER, FEEDRSS e EXCELIMPORTER
  (fora da amostra mínima); a verificação cruzada com eles ficou limitada ao que os
  documentos amostrados afirmam.
- Não foi executado nenhum migration/teste; a análise é estática, como exige o corpus.

## 7. Veredito

**Aprovado com ressalvas.** Os documentos da amostra são substancialmente aderentes ao
código, com as quatro dimensões preenchidas, trechos úteis, receitas corretas e leitura
cruzada coerente; não há erro de segurança na descrição dos mecanismos. Antes do merge da
Onda 2, corrigir **G2-01 a G2-04** (P2) e, na mesma passagem, G2-05 a G2-08 e G2-10 (P3).
O gate G2 **não fecha formalmente** enquanto G2-09 não for resolvido (consolidação de
`_indices/servicos.csv` com 13 linhas e atualização do índice de rotas da Gallery).
