# Lições da Execução Real

Armadilhas observadas de fato na execução do dossiê do Portal Municipal.
Cada uma: **sinal**, **causa**, **correção**, e **onde o protocolo a
previne**.

---

## 1. Premissas do enunciado podem estar erradas

**Sinal.** O pedido descreve o sistema com confiança e detalhes que o
código não confirma.

**Casos reais (3):**
1. *"O módulo `a-cidade` foi renomeado"* → o seed renomeia a **categoria 6**
   (`a-prefeitura` → `a-instituicao`); o slug `a-cidade` é idêntico nas
   duas versões.
2. *"Transparência Fiscal tem tabelas de despesas"* → é um catálogo de links
   sobre `contents`, sem tabela própria.
3. *"`enquetes` usa `questions`/`options`"* → esse motor é do módulo
   *Revisões*; `enquetes` não usa.

**Causa.** Quem escreve o pedido conhece o negócio, não o código. A
terminologia diverge.

**Correção.** Registrar as premissas na F0 e verificá-las na F1/F2. No
dossiê, a **divergência** é documentada, não a premissa.

**Prevenção.** Princípio **P5**.

---

## 2. O nome do registro não é o nome do módulo

**Sinal.** Dois slugs que descrevem a mesma capacidade:
`diario-ofifical` (typo) e `diario-oficial`.

**Causa.** Seeds escritos à mão, com revisão insuficiente. O typo sobrevive
porque o menu do CMS usa o slug e nada o valida.

**Correção.** Um dossiê, com **alias** explícito na configuração do
gerador de índice. O alias é visível, não escondido.

**Prevenção.** Teste **C3** em
[`04-DECISAO-MODULOS.md`](04-DECISAO-MODULOS.md).

---

## 3. Registrador de módulo pode divergir do código — nos dois sentidos

**Sinal.** Módulo com `status = false` some do menu. Módulo no registro sem
controller. Controller sem entrada no registro.

**Casos reais.** 28 módulos com `status = false` (ocultos do menu);
`viap` e `carta-de-servicos` criados por seed **depois** da migration;
`relatorio-estatistico-ouvidoria` com só o CMS na v5 e nada na v6.

**Causa.** O registro é maintainido à mão e diverge do código ao longo do
tempo.

**Correção.** O inventário mecânico (F1) é o contraponto: a lista canônica
(F2) é a **união** do registro com o inventário, com a **divergência
classificada**.

**Prevenção.** Testes **C1** e **C2**.

---

## 4. A análise estática erra a severidade

**Sinal.** "SQL injection em 8 pontos" — mas só 3 eram publicamente
exploráveis; os outros exigiam sessão autenticada.

**Causa.** O leitor vê a concatenação de SQL e não vê a cadeia de
middleware que antecede a rota.

**Correção.** Separar **defeito** (a concatenação existe) de **exposição**
(a rota é alcançável por quem). E, para críticos, executar.

**Prevenção.** Princípio **P3**.

---

## 5. Bug crítico não reproduzido muda o texto do bug

**Sinal.** A escalada de privilégio foi registrada como "análise estática".
Ao reproduzir, apareceram fatos novos.

**O que a execução mudou:**
- `.create` e `.edit` **não** são liberados pelo bug do `array_where` — só
  `.store`, `.update` e `.destroy`. (O `array_where` compara **valor**
  contra a lista de valores, e `create`/`edit` são apenas *chaves*.)
- O `UserController@store` **tem** guarda interna (`has_access('usuario.create')`),
  então ele não é o elo mais fácil — o elo é o `RoleController@store`, que
  **não** tem guarda nenhuma.
- O resultado prático é pior do que o texto original dizia: a escalada
  entrega também a **leitura do log de auditoria** (`/cms/log`).

**Lição.** O texto do bug deve ser escrito **depois** da reprodução, não
antes. Um registro escrito a partir de leitura é hipótese.

**Prevenção.** Princípio **P3** + F5 escreve a versão final depois do teste.

---

## 6. O erro mais caro é o falso positivo do gate

**Sinal.** O `auditar.py` acusou `[MAPA v5->v6] ausente` em módulos que
já tinham o mapa — porque ele só olhava o `08-GAPS-E-DIVIDAS.md`, e o mapa
estava num `09-`.

**Causa.** O gate codificou a estrutura **esperada** em vez da estrutura
**permitida**.

**Correção.** O gate passou a varrer **todos** os `.md` da pasta.

**Causa raiz.** Gate que falha por motivo errado **treina o executor a
ignorar o gate**. Um gate ruidoso é pior que gate ausente.

**Prevenção.** Princípio **P7** + a F6 sempre revalida depois de rodar o
gerador.

---

## 7. `grep` de extensão: ordem altera o resultado

**Sinal.** Auditoria acusou `.../config.json` como inexistente.

**Causa.** O `grep` do shell resolve *leftmost-first* (não *leftmost-longest*).
Com `js` antes de `json` na alternância, `foo.json` casa `foo.js`.

**Correção.** Extensão mais longa primeiro (`json` antes de `js`) e uso de
`-oh` sem `-c`, para que o valor capturado seja o caminho inteiro.

**Prevenção.** O `auditar.py` usa expressão regular com alternativa
ordenada; a checagem de shell está no contrato com a mesma ordem.

---

## 8. Soft delete esconde lixo do banco

**Sinal.** Depois de remover o usuário de teste, `users.deleted_at` estava
preenchido — a linha sumiu da aplicação mas continuava no banco — e
`role_users` ficou órfão.

**Causa.** `SoftDeletes` no modelo, e a verificação de limpeza olhou a
aplicação, não a tabela.

**Correção.** A verificação de estado (P4) é feita por **contagem direta no
banco**, não pela aplicação. E a remoção de vínculo precede a remoção do
usuário.

**Prevenção.** Princípio **P4** + roteiro de limpeza na F5 §5.

---

## 9. Escalar privilégios deixa rastro no banco

**Sinal.** A escalada de privilégio exigiu criar 1 usuário, 2 perfis e 2
vínculos.

**Causa.** A cadeia de ataque é, por definição, uma sequência de escrita.

**Correção.** O caderno de sondas registra a **contagem antes** de cada
etapa, para que a limpeza seja verificável e não "achismo".

**Prevenção.** Princípio **P4**.

---

## 10. Agente sem fatia auto-contida reconstrói convenções

**Sinal.** Dossiês de agentes diferentes com tabelas de rotas em formatos
diferentes, seções nomeadas de formas diferentes, sometimes in English.

**Causa.** O agente recebeu "documente o módulo X" sem saber o padrão.

**Correção.** Contexto + contrato + alvo + critério de aceite, no prompt.
O contrato de 9 arquivos é o que **uniformiza**: o gate cobra os 9 pelo
nome, então a variação fica só dentro do conteúdo.

**Prevenção.** Princípio **P8**.

---

## 11. A primeira versão do gate usa o nome do arquivo como se fosse o único lugar

**Sinal.** Auditou "mapa ausente" porque o mapa estava no `09-`.
(Ver lição 6 — mesma raiz.)

**Regra geral:** **o gate verifica o que o contrato exige, não onde ele
está.** Exigência ("tem mapa entre versões") ≠ implementação ("no arquivo
08").

**Prevenção.** Ao escrever um gate, separar *requisito* de *local*; o local
só é exigido quando o contrato o fixa.

---

## 12. Escrever o gate depois de escrever o dossiê é tarde

**Sinal.** O `auditar.py` nasceu para consertar problemas específicos já
vistos (caminho inexistente, placeholder, CJK, tamanho, mapa).

**Causa.** Não havia especificação de qualidade antes de produzir.

**Correção (para os próximos projetos).** Definir `05-CALIDADE.md` **antes**
da F3 e deixar o gate escrito — a lista de checks existe desde o começo e
só cresce com os casos novos.

**Prevenção.** O gate nasce com a F3, não depois dela.

---

## 13. A ferramenta pode clobberar um documento curado

**Sinal.** `inventario.py` escreveu por cima de
`_00_panorama/10-INVENTARIO-DE-MODULOS.md`, que era um documento **curado**
(a tabela canônica de 56 módulos, escrita à mão). O arquivo não está em
git — a perda foi definitiva.

**Causa.** A ferramenta usava o nome do documento curado como nome de saída.
Como a primeira versão do protocolo ainda não existia, a colisão só ficou
visível depois.

**Correção.** Duas camadas:
1. A ferramenta declara seus **artefatos próprios** (`proprios`) e os
   sobrescreve sempre; qualquer outro arquivo exige `--forcar`.
2. A separação entre **documento gerado** e **documento curado** é explícita
   no nome: `10-INVENTARIO-DE-MODULOS.md` (canônico, gerado a partir da
   configuração) e `11-INVENTARIO-MECANICO.md` (volumetria bruta, gerado).

**Princípio geral.** *Ferramenta que escreve em diretório de trabalho
precisa declarar o que é dela.* O inverso — "a ferramenta escreve onde
quer" — é como um documento curado desaparece sem aviso.

**Prevenção.** Geração ≠ curadoria, sempre.

---

## 14. A ordem das colunas do registro varia por projeto

**Sinal.** O inventário listou `Configurações da Instituição` como **slug**,
e `1` como nome. Os 58 módulos saíram errados.

**Causa.** O parser assumia `id | nome | slug | status` fixo, herdado do
`psql` do Portal Municipal. O `grep` do seed do TecVoto devolve uma coluna
só.

**Correção.** `modulos.campos` na configuração declara a ordem das colunas;
o parser monta um dicionário por nome de campo. O `separador` também é
configurável (o TecVoto não usa separador).

**Princípio geral.** *Não assuma o formato da entrada — declare-o.*

---

## 15. O escopo de um gate precisa ser explícito quando a base é compartilhada

**Sinal.** Ao mapear o primeiro projeto novo (`cms`), a auditoria do Portal
Municipal passou a acusar `[ESTRUTURA] cms: falta [...]` e
`[MAPA v5->v6] ausente na pasta cms/` — o auditor varria
`dossie_modules/**` recursivamente e estava lendo o dossiê do outro projeto.

**Causa.** A base de dossiês passou a ter **vários projetos** dentro do
mesmo diretório. O glob recursivo não distingue "dossiê deste projeto" de
"dossiê do projeto vizinho".

**Correção.** O auditor calcula o conjunto de diretórios **estrangeiros**
(a partir de `projetos.json`) e os exclui. O escopo passa a ser: pastas de
módulo + pastas auxiliares `_xxx` do próprio projeto.

**Princípio geral.** *Em base compartilhada, todo gate precisa saber o que
está fora do seu escopo.*

---

## 16. Arquivo gerado não é candidato a gate de qualidade

**Sinal.** O inventário mecânico tem 2.000 linhas (é uma tabela grande) e o
novo projeto tinha um inventário de 26 linhas (projeto pequeno). Os dois
foram reprovados no gate de tamanho — um por ser grande demais, outro por
ser pequeno demais.

**Causa.** O gate de qualidade e o gate de *forma do artefato gerado* são
coisas diferentes. O tamanho de um arquivo gerado é consequência da
extensão do projeto, não defeito.

**Correção.** O gate de tamanho só vale para dossiê **entregue** (projeto
com estado `completo` ou `em_andamento`) e pula arquivos gerados, marcados
por nome e posição.

**Princípio geral.** *Qualidade mede o trabalho humano, não o output da
ferramenta.*

---

## 17. Gate específico de uma topologia não vale para todas

**Sinal.** O gate "mapa entre versões" acusava ausência em `cms/`,
`sucesu-cms/` e `tecvoto/` — projetos de **versão única**, onde não existe
comparativo a fazer.

**Causa.** O gate foi escrito para o caso do Portal Municipal (v5 → v6) e
transportado sem condição.

**Correção.** O gate só roda quando o projeto tem **duas ou mais
versões**. O gate de cobertura do registro só roda quando o estado é
`completo` — em fase de preparo, ele acusaria a ausência de dossiês que
ainda nem foram pedidos.

**Princípio geral.** *Um gate carrega as premissas do caso para o qual foi
escrito. Declare-as.*

---

## 18. A ordem das verificações de arquivo esconde bugs de classificação

**Sinal.** O extrator reportou **0 views** no Portal Municipal, que tem
1.659 arquivos `.blade.php`.

**Causa.** O classificador testava `f.endswith('.php')` **antes** de
`.blade.php`. Como `.blade.php` termina com `.php`, o arquivo entrava no
ramo errado, era tratado como classe PHP, não achava `class` e caía fora.

**Correção.** `.blade.php` é testado primeiro.

**Princípio geral.** *Em classificadores por sufixo, o sufixo mais longo
precisa ser testado antes do mais curto.* É o mesmo cuidado do `grep` com
`json` e `js` — a mesma classe de bug, em contexto diferente.

