# Prova: Mutação

> Escopo: Gate 3 (avaliador independente). Não é dimensão do score composto — o composto continua
> `arquitetura + contratos + qualidade + testes`. Esta prova impõe **teto** em Cobertura de Testes
> (tabela em `evals/rubrics/test-coverage.md`, seção "Tetos de prova").

## O que esta prova responde

Um teste verde prova que o código roda. Só a **mutação** prova que o teste MORDE: quebre de
propósito a linha que carrega a decisão e veja quem grita. Se ninguém grita, a suíte é decoração —
e foi assim que 11 testes verdes cobriram um artefato que o build recusava.

A pergunta desta rubrica é sempre a mesma: **você mutou o call site que carrega a decisão, rodou a
suíte inteira, leu o MOTIVO da falha, e devolveu o arquivo byte a byte?**

## Bandas 0-10

| 0-3 | 4-6 | 7-8 | 9-10 |
|---|---|---|---|
| Nenhuma mutação. Ou mutação declarada sem diff, sem md5 e sem testes nomeados — afirmação, não prova. Ou revert por `git checkout --`, que descarta trabalho não commitado de terceiros | Mutou, mas no lugar errado (o `if` refatorado, não o call site que decide) ou com filtro estreito ("derrubou 3" medido sobre 40 testes de um arquivo só). Reportou md5 sem ler o motivo da falha | Mutação no call site certo, suíte inteira, diff e md5 antes/depois. Falta o MOTIVO literal da falha, ou uma cláusula de um predicado composto ficou sem rodada própria | Uma rodada por cláusula, cada uma com arquivo:linha, diff, md5 antes/depois, contagem sobre a suíte INTEIRA e a mensagem literal de falha. Revert por snapshot de bytes + `os.utime`, com `git status --porcelain src/` vazio ao fim |

## Anti-padrões literais

- **Mutar o `if` refatorado em vez do call site que carrega a decisão** — o extract-method moveu a
  regra; o teste continua batendo no invólucro. Ancore onde o valor é DECIDIDO. (memória: guard do
  remédio, cuja checagem real vivia duas camadas acima do `if` mutado)
- **Rodar com filtro e reportar "derruba N"** — "3 de 40" e "3 de 2684" são afirmações diferentes.
  Só a suíte **inteira** autoriza a frase "derruba N". (memória: F30, filtro no Gate 3)
- **Filtro tão estreito que a prova fica vácua** — mutar e rodar só o teste que você já sabia que
  quebraria não testa nada além da sua própria expectativa.
- **Aceitar um seed sem perguntar "que sequência real produz este seed?"** — fixture que só existe
  no teste prova o teste, não o sistema.
- **Imprimir `diff` + md5 e parar por aí** — md5 prova que ALGO mudou, nunca O QUÊ. Sem a mensagem
  literal de falha (`expected 3, got 0` em `x.test.js:88`), a prova é de que o arquivo mudou, não de
  que o teste morde a regra. (memória: três rodadas de correção nesta epic)
- **Reverter por replace reverso, ou por `git checkout --`** — o replace reverso erra em arquivo com
  ocorrências repetidas; o `git checkout --` **apaga alterações não commitadas de outra pessoa** e
  aconteceu de verdade num Gate 3 desta epic. Reverta por **snapshot/restore de bytes** (cópia feita
  ANTES) + `os.utime` para devolver o mtime, e confirme por `md5sum`.
- **Escrever a mutação por heredoc do shell quando ela contém barra invertida** — o transporte come
  o escape: `\b` vira o byte `0x08`, `\n` vira quebra de linha real, e o regex mutado **não é o que
  você escreveu**. Sintoma: a mutação parece aplicada e o resultado não muda, ou o arquivo quebra
  num ponto que você não tocou. Diagnóstico em um comando: `cat -A` mostra `^H` onde devia estar
  `\b`. Saída: patch por **índice de linha** com o conteúdo montado sem barra invertida no fonte
  (`String.fromCharCode(92)`), ou ferramenta de edição estrutural — nunca heredoc.
  (memória: **três agentes** desta epic tropeçaram nisto; dois descartaram o resultado, um só
  descobriu ao conferir os bytes)
- **Mutar antes de commitar o fix** — se a sessão morrer no meio, o que sobrevive é a mutação, não a
  correção. **Commite o fix ANTES de mutar**, sempre.
- **Ler mais do que a primeira assertiva que falha** — a mutação só prova até ali; o resto do teste
  nem rodou. Não conte cobertura que a mutação não exercitou.
- **Uma rodada só para um predicado `A && B`** — derrubar `A` não diz nada sobre `B`. **Uma rodada
  por cláusula.** O mesmo vale para um guard de duas classes: toque **cada** classe.
- **Deixar loader, validador e superfície de publicação fora do alvo** — o defeito costuma estar em
  quem LÊ o arquivo, não em quem o escreve. (memória: 11 testes verdes contra o render do próprio
  gerador, nenhum deles CARREGANDO o artefato)
- **Fixture fora da ordem de grandeza da tese** — tese sobre 2 600 testes provada num fixture de 3.
- **Mutação de prosa presa por regex** — texto de skill/rubrica não se prende por `assert.match` de
  vocabulário: mude a FORMA (mova a seção, troque o cabeçalho) e veja se o canário ainda morde.
- **Número de mutação sem SHA e sem data** — "derruba 12" envelhece em uma semana. Anote o SHA
  revisado e a data ao lado do número.
- **Terminar sem `git status --porcelain src/` vazio** — mutação esquecida em produção é o pior
  desfecho possível deste método, e já aconteceu com avaliador morto no meio da rodada.

## Como registrar no relatório

Na seção `## Mutações` do `evaluator-report.md`, uma linha por mutação, com os cinco campos:

```markdown
| # | Arquivo:linha | Mutação (diff) | md5 antes | md5 depois | Testes derrubados | Motivo da falha |
|---|---|---|---|---|---|---|
| M1 | src/lib/x.js:42 | `- if (a && b)` / `+ if (a)` | abc… | def… | 3 de 2684 | `expected 3 got 0` em x.test.js:88 |
```

E, ao fim da seção: **Reversão:** snapshot/restore de bytes + `os.utime`; `git status --porcelain
src/` vazio. Feature sem código de produção declara a seção com a linha
`não aplicável — feature sem código de produção`.
