- Aug 07, 2026
-
-
Igor Matheus authored
34 casos verdes (26 de security rule, 8 de integração com a Function), suíte de 66. São as duas regras que, quebradas, corrompem o bolão — e as duas deixaram de ser código de aplicação. Descoberta que muda o app: rules não são filtros MG-3-07 falhou com "Property userId is undefined on object. for 'list'", que parecia bug de rule e não era. Numa consulta, a rule é avaliada contra a consulta: o `where` precisa provar o acesso. Filtrar palpites só por matchId deixa userId e matchFinished indefinidos para o avaliador, e ele recusa — mesmo que todos os documentos devolvidos fossem legíveis. Consequência concreta para MG-7: a tela de "palpites de todos" precisa consultar matchId == X E matchFinished == true. O caso ficou com as duas metades (sem o filtro falha, com ele passa) para a exigência não se perder. O risco do sigilo mudou de lugar, e os testes seguem ele No NestJS era filtro em consulta: código certo, regra válida sempre. Aqui é um campo que alguém precisa virar, e esse alguém é uma Function. Então os casos testam a transição: o apito não libera, FINISHED sem placar não libera, e MG-3-30 verifica a Function não rodando — palpite fica secreto a mais, nunca exposto antes. O inverso não tem como acontecer: só a Function escreve o campo. Defeito no teste, não no produto: os dois casos negativos falharam por timeout do Jest (esperar 5s com limite de 5s). Caso negativo com orçamento de tempo errado parece defeito de regra, e o reflexo de mexer na regra é forte. Limite registrado: o índice de grupo em palpites.matchId está declarado e não verificado — o emulador não exige índice e a Function de sigilo depende dele. Verificação obrigatória em MG-8. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
28 casos de security rule verdes. A autorização deixa de ser código de aplicação e passa a ser regra do banco: quem não participa não lê, quem saiu perde o acesso na hora, participante não se promove, e `createdBy` é imutável — que é o que sustenta "criador é admin permanente" (CC-8) por um caminho que nenhum teste de CC-8 cobria. O schema vive em functions/src/modelo.ts: o Firestore não tem schema, então esse arquivo é o contrato que as rules assumem. Rule que consulta `role` e documento que grava `papel` é furo de autorização silencioso. Duas correções de desenho, as duas achadas por teste que falhou - "Meus bolões" não pode ser consulta de grupo em `membros`. Falhou duas vezes e cada falha ensinou: primeiro que consulta de grupo não é avaliada pelo match aninhado (caiu no deny genérico), depois que no `list` o {uid} do caminho não é vinculado e a rule não inspeciona o `where` — não há como provar que a pessoa pediu só o que é dela. Virou índice sob o próprio usuário, com o resumo dentro: uma leitura, rule sem get(). O spike foi corrigido. - O displayName mudou de lugar por causa do sigilo: ler o perfil de cada participante seria leitura extra e furaria a regra de não ler perfil alheio. 18 dos 28 casos verificam recusa, de propósito: 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. Limite registrado: o emulador não exige índice composto, então consulta que precisaria de um passa aqui e falha em produção. Entra como verificação obrigatória de MG-8 contra o projeto real. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
O dono habilitou o Blaze e autorizou publicar a função trivial `saude` da MG-1 para exercitar o caminho inteiro antes de a MG-6 depender dele. Funcionou — o que prova o Blaze, já que Functions não sobem no Spark — e respondeu 200 em 0,5s. O deploy pegou dois problemas que nenhum teste local pegaria: - A Function foi para us-central1 com o Firestore em southamerica-east1. É o padrão do Firebase e ninguém notaria: no emulador tudo é localhost. Em produção, cada leitura da apuração e da sincronização cruzaria o continente, nas duas operações que fazem mais leituras. Fixada a região em setGlobalOptions; a função de Iowa foi removida (confirma 404). - Nenhuma política de limpeza no Artifact Registry: cada deploy acumula uma imagem, para sempre. Fatura crescendo em silêncio é o oposto da restrição de custo. Configurada limpeza de imagens com mais de 3 dias, nas duas regiões. Também ligados delete protection e point-in-time recovery no Firestore (retenção de 1h para 7 dias), com o banco ainda vazio — o momento mais barato. Custo residual honesto: centavos de dólar por mês de armazenamento das imagens dentro da janela de 3 dias. Não é zero. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
O dono trocou a conta do Firebase (igorengcomp12@gmail.com) e criou o projeto definitivo. O bolao-raro-ad0fc da conta corporativa foi abandonado antes de receber qualquer dado. Estado verificado: Firestore já provisionado em modo nativo na região southamerica-east1 — irreversível, e é a certa para um bolão do Brasileirão, onde o momento crítico é o minuto antes do apito. Registrado no spike o que precisa de atenção antes de existir dado que importe: delete protection e point-in-time recovery vêm desligados, e o projeto está numa conta pessoal em vez da corporativa — titularidade fica com a pessoa, não com a empresa. O default do .firebaserc continua demo-bolao; o real entra como alias `raro`. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
bolao-raro-ad0fc entra no .firebaserc como alias `raro`. O `default` continua sendo demo-bolao, para comando esquecido sem --project cair no emulador em vez de produção. Estado verificado do projeto real: nenhum app registrado, Firestore não provisionado (API desabilitada), Auth sem configuração. Projeto limpo — e provisionar o Firestore exige escolher região, que é irreversível, então não foi feito sem confirmação. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
bolao-firebase/ com firebase-tools 15.26.0 pinado como devDependency — global não é reprodutível para quem clona. Firestore, Auth e Functions em emulador, portas fixas, e 4 testes de security rule provando que as rules começam fechadas. Duas travas de propósito - Projeto `demo-bolao`: o Firebase trata prefixo `demo-` como demonstração e o SDK nunca fala com serviço real. Apontar para projeto de verdade exige PERMITIR_PROJETO_REAL=true. - Rules `if false` na largada. Participação entra em MG-2, sigilo e prazo em MG-3. Abrir primeiro e fechar depois é como vazamento chega em produção. Dois defeitos encontrados no próprio script, os dois no caminho de erro - `command -v java` mente no macOS: existe um stub em /usr/bin/java que satisfaz a checagem e falha ao executar. O teste passou a ser rodar `java -version` — presença de binário não é funcionamento. - Sem JDK algum, o script morria com "unbound variable" (array vazio sob set -u) em vez de mostrar a instrução. Foi para poder testar esse caminho que a lista de candidatos virou JDK_CANDIDATOS sobrescrevível. Confirmado: nada aqui exigiu plano Blaze. O cartão só entra no deploy. MG-1 fechada, 8/8 tasks, plano e execução em docs/testes/MG-1-*.md. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
Decisão do dono em 07/08: Firestore + Cloud Functions, migrando antes de desligar — as duas implementações coexistem até os 234 casos e2e passarem na nova. Spike de arquitetura (docs/spikes/backend-firebase.md) - Dimensiona o que sai: 38 rotas, 17 services, 12 tabelas, 3 jobs, 5.139 linhas. O caro não é o código, são os 234 e2e que provam que prazo, sigilo e apuração valem. - Resolve o problema central: security rules não filtram resultado de query, então o sigilo do palpite passa a ser flag desnormalizada virada por Function no fim do jogo. O risco muda de "a consulta vaza" para "a Function não rodou". - Descoberta que muda o sequenciamento: o Emulator Suite roda local sem cartão, então MG-1 a MG-8 não dependem de decisão financeira. Blaze só para publicar. - Mapa das 12 tabelas para coleções, com o ranking materializado — somar pontos na leitura estouraria o free tier. PRD e 9 stories (73 tasks), na ordem do risco e não da facilidade: emuladores, modelo e rules, sigilo e prazo, Auth com recuperação de senha, apuração, jobs no Scheduler, app, paridade verificada, e só então o corte do NestJS. Efeitos colaterais registrados: MG-4 faz AC-4 voltar do cancelamento (o Auth dá recuperação de senha de graça) e MG-6 fecha PP-10 OPS-1 e as 4 tasks de PP-8 presas ao FCM. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
O arquivo era o template do GitLab explicando como subir arquivos. Trocado por uma porta de entrada real: as três partes do monorepo, as seis regras de domínio que quebram a competição se forem violadas (prazo pelo servidor, sigilo até o fim do jogo, empate sem faixa de saldo, campeão nunca congelado, saída definitiva, tudo adiado) e onde ler o processo. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
Produto completo de bolão de futebol, construído do PRD ao device: 35 das 38 stories fechadas, 312 de 318 tasks, três PRDs concluídos (Identificação e acesso, Criação e convite, Ciclo de vida do bolão). Backend (NestJS 11 + Prisma 7 + Postgres) - Palpite com prazo decidido pelo relógio do servidor, sigilo até o fim do jogo, apuração 10/5/3 lida da configuração do bolão, ranking com desempate por critério configurável, extrato explicando cada ponto. - Ciclo de vida: encerramento normal e antecipado, campeão derivado na leitura (nunca congelado), arquivamento reversível, recriação de temporada herdando regras e galera. - Fonte oficial de calendário e placar via porta `FonteDeDados`: adaptador falso para desenvolvimento e `FootballDataAdapter` (football-data.org v4) para produção. O limite de 10 req/min desenhou o adaptador — uma requisição por temporada, cache de 5 min, cota recusada antes de ser gas...
-
Igor Matheus authored
-