# Execução — `MG-2` Modelo no Firestore e regras de participação

**Plano:** `MG-2-plano.md` · **Data:** 07/08/2026
**Ambiente:** emulador do Firestore (`demo-bolao`), `@firebase/rules-unit-testing`.

**Resultado: 28/28 ✅** (mais os 4 casos de `MG-1`, total 32 na suíte) — e **duas
correções de desenho**, as duas encontradas por teste que falhou.

## Correção 1 — consulta de grupo não serve para "meus bolões"

O desenho do spike previa a lista da tela inicial como **consulta de grupo** em
`membros`, filtrando `userId == meu uid`, com o resumo do bolão desnormalizado
dentro do vínculo. Escrevi a rule que parecia óbvia:

```
match /{caminho=**}/membros/{uid} {
  allow read: if request.auth.uid == uid;   // errado
}
```

O caso `MG-2-26` falhou duas vezes, e cada falha ensinou uma coisa:

1. **`false for 'list'`** — consulta de grupo **não** é avaliada pelo `match`
   aninhado de `/boloes/{poolId}/membros/{uid}`. Ela caiu no `deny` genérico. Sem o
   teste, a tela inicial simplesmente não carregaria em `MG-7`, e o diagnóstico
   seria "o Firestore não deixa" em vez de "falta uma rule de grupo".
2. **`Null value error`** — com a rule de grupo declarada, o `{uid}` do caminho
   **não é vinculado** numa operação de `list`: não existe documento único para
   casar. E rule não consegue inspecionar o `where` da consulta, então **não há
   como provar** que a pessoa pediu só o que é dela.

A conclusão é de modelo, não de rule: a lista virou um **índice sob o próprio
usuário** — `usuarios/{uid}/boloes/{poolId}`, com o resumo do bolão dentro. A
consulta passa a ser no caminho da pessoa, a rule decide sem `get()` nenhum, e
continua sendo uma leitura só. O preço é manter a cópia em dia, e quem faz isso é
Function (`MG-5`/`MG-6`).

O spike foi atualizado: o mapa de dados dizia que o resumo ficava no vínculo.

## Correção 2 — o `displayName` mudou de lugar por causa do sigilo

Com o resumo saindo do vínculo, sobrou a pergunta de onde a lista de participantes
pega o nome de cada um. Ler `usuarios/{uid}` de cada participante seria leitura
extra por pessoa **e** furaria `MG-2-24`, que é a regra de ninguém ler o perfil
alheio. O nome fica copiado no vínculo e na linha da classificação.

## Sobre índices compostos (`BE-5`)

**Nenhum é necessário ainda**, e isso está declarado em vez de suposto: as
consultas de `MG-2` são leitura de documento, listagem de subcoleção e ordenação
por campo único (`position`), todas atendidas pelo índice automático. As compostas
aparecem em `MG-3` (palpites por jogo e por estado).

⚠️ **Limite conhecido do ambiente:** o emulador **não** exige índice — ele cria na
hora. Consulta que precisa de índice composto passa aqui e falha em produção com
`FAILED_PRECONDITION`. Isso não tem como ser pego em emulador; entra como
verificação obrigatória de `MG-8`, contra o projeto real.

## Observações

- **A maioria dos casos verifica recusa** (18 de 28). No NestJS, negar era uma
  linha de service com e2e; aqui é a **ausência** de uma cláusula em rule — e
  ausência não aparece lendo o arquivo, só falhando um teste.
- `MG-2-25` trava o que ainda não existe: palpite fechado até `MG-3`. É o caso que
  impede alguém de abrir a coleção "só para o app funcionar".
- `MG-2-09` (`createdBy` imutável) é o que sustenta "criador é admin permanente":
  se o campo pudesse ser reescrito, um segundo admin se declararia criador e a
  regra de `CC-8` cairia por um caminho que nenhum teste de `CC-8` cobria.
