- Aug 07, 2026
-
-
Igor Matheus authored
228 casos verdes no Firebase (16 arquivos). Entram 34 casos de paridade com o id de origem preservado e a medição de custo. O mapa é script, não tabela docs/cobertura.py varre os planos da fase 1 e diz onde cada caso é verificado hoje. Existe como script porque o risco escrito na própria story é "é a story mais fácil de fingir que foi feita", e tabela escrita à mão envelhece em silêncio. O critério é rigoroso de propósito: só conta como verificado quando o id aparece num teste da stack nova. Resultado honesto: 188 de 420 (44%). Os 231 restantes não são um bloco só, e tratá-los como um seria o erro que a story avisa: casos de tela cujo teste existe e passa mas não cita o id; casos cujo mecanismo deixou de existir (rotação de refresh, rate limit de login, "não há recuperação de senha") — reescritos e registrados um a um no mapa, nunca descartados em silêncio; e o grupo que falta reexecutar, onde CV-2 e PP-8 estão em zero. 34 casos reexecutados com o id original PP-7-05 continua sendo PP-7-05. Foi escolhido primeiro o grupo de sigilo, prazo, papel e autorização — o que sumiria primeiro se "os testes passam" bastasse como prova. Onde o meio mudou, o caso diz qual é: no NestJS recusava um if num service, aqui recusa o banco ou a callable. Para quem chama a API direto a diferença é grande — não existe mais resposta para filtrar. Custo medido, não estimado Contador em volta do Firestore, rodando os fluxos de verdade. O ranking geral custa 50 leituras para uma página de 50 num bolão de 60: uma por linha, não por participante nem por palpite. Somar na leitura seria 60 x 10 x 38 documentos por abertura de tela. Um bolão de 60 pessoas gasta ~5.150 leituras por rodada, com folga de 10x no free tier. Dois testes meus dependiam de banco recém-criado: contavam avisos e participantes em bolão de id fixo, e o emulador que fica de pé para o teste em device acumula a execução anterior. Passaram a criar bolão e participante novos a cada rodada. Seguem travando a paridade de AC-2 as duas decisões do dono: email enumeration protection e Identity Platform. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
244 testes de widget passando sem mudança de asserção, mais 4 casos em device contra os emuladores e a fonte oficial de verdade. Falta só FE-6 (remover o ApiClient), que espera MG-8. A troca não vazou para a apresentação, e dá para provar O que mudou nos 235 testes de widget foi uma palavra por arquivo: o falso passou a estender XRepositoryHttp em vez de XRepository, porque o nome de sempre virou contrato com duas implementações. Nenhuma tela, nenhum estado, nenhuma mensagem. O que sustentou isso foi manter os modelos intactos — eles são o formato que a tela conhece, e é por isso que MG-2 gravou os campos em inglês. Um dono só para o offline A story avisava que o cache muda de dono. O Firestore tem persistência local, mas responde dado antigo sem dizer que é antigo; o CacheLocal grava a idade e a mostra. Persistência do Firestore desligada: ter os dois seria ter duas respostas para a mesma pergunta, uma sem rótulo. O relógio virou desvio, não instante: a hora do servidor vem de uma callable uma vez por sessão, descontando meia viagem de rede. Falhar não derruba nada — sem medida a contagem cai no aparelho, e quem recusa continua sendo a rule. Quatro coisas que só o device mostrou Os 188 casos do servidor e os 244 de widget passavam com tudo isto quebrado: cleartext HTTP bloqueado pelo Android desde a API 28 (liberado só na variante de debug, e só para o emulador — abrir isso em release trocaria um problema de desenvolvimento por um furo de verdade); o SDK Android validando o formato da chave de API antes de qualquer chamada, com 40 caracteres em vez de 39; quem se cadastrava como "Bruno" virando "bruno1786151672" no bolão, porque o gatilho de perfil dispara antes de o displayName existir; e a tela do extrato quebrando em vez de mostrar erro quando a falha não era ApiError. E dois defeitos do meu próprio teste em device: ele herdava a sessão da execução anterior (o Firebase persiste o login), e o caso do apito se auto-pulava quando não sobrava jogo começado — caso que pula sozinho não protege nada. A cota da fonte aparece ao rodar em série: repetir o arquivo no mesmo minuto faz a criação de bolão falhar, e isso é o adaptador recusando antes de gastar as 10 requisições por minuto, que é o comportamento de PP-10. Registrado para MG-8: o Firestore não tem offset, então a paginação do ranking lê do começo e descarta — aceitável com dezenas de participantes, e trocar o contrato da tela por cursor é outra story. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
188 casos verdes em 14 arquivos. Entraram regras, ciclo de vida, correção manual, extrato e ranking da rodada — que fecham junto as tasks BE-4, BE-5 e BE-6 de MG-5. No app entrou só a configuração: SDKs do Firebase e apontamento por --dart-define, com o emulador em opt-in (esquecer o define num build de release tem de cair no ambiente de verdade, não num emulador que não existe no aparelho de ninguém). Um furo do CC-3 que só apareceu ao ligar o app A rule de MG-2 deixava o admin escrever direto no documento do bolão, e o caso MG-2-07 afirmava isso como correto. Rule não consegue olhar se já existe palpite no bolão — ou seja, dava para trocar a tabela de pontos depois do primeiro palpite, que é exatamente o que a trava existe para impedir. Bastaria esperar os palpites e ajustar a pontuação para o que o palpite do admin acerta; o sigilo de PP-7 não cobre isso. A escrita direta no bolão passou a ser negada por inteiro e o MG-2-07 inverteu. Vale igual para closedAt e archivedAt: encerrar exige não haver jogo pendente, e encerramento antecipado precisa ficar marcado como tal. O que não foi portado, e por quê O encerramento antecipado do NestJS empurrava o kickoffAt dos jogos abertos para fechar o prazo. Aqui isso seria defeito: o jogo é global e o horário dele vale para todos os bolões do campeonato. A rule do palpite já recusa bolão encerrado. A consolidação do aviso virou fusão PR-7 pede aviso consolidado, e no NestJS isso era um coletor em memória da sincronização. Esse coletor não existe em Functions: cada jogo corrigido é uma invocação separada, possivelmente em outra máquina. Aviso de correção não lido e recente do mesmo bolão passa a receber o jogo novo em vez de virar outro. A correção manual também deixou de devolver quantos avisos saíram — quem apura é gatilho, e esperar por ele seria segurar o admin numa tela por um número. Ela devolve quantos palpites serão reapurados. Três testes meus, três defeitos meus, nenhum do produto: MG-7-62 esperava pela categoria do efeito (existe aviso) e não pelo efeito, e Ana já tinha um aviso antigo; MG-7-63 esperava pelo ponto para depois exigir fusão, mas o ponto é escrito antes do aviso, então o teste criava a corrida que denunciava; MG-7-64 testava cenário impossível, porque em bolão pequeno toda correção mexe na posição de todo mundo. E um quarto, de vizinhança: o calendário é global, então dois arquivos de teste na mesma rodada de BSA disputavam os mesmos jogos — cada um passou a ter o seu código de campeonato. Falta a camada de dados do app (FE-1, FE-2, FE-4 a FE-6) e a verificação em device. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
158 casos verdes em 12 arquivos. 26 novos, cobrindo criação, convite, entrada, saída e papel. A lacuna apareceu ao ligar o app MG-2 modelou as coleções e escreveu as rules que protegem cada uma. MG-3 fechou o palpite, MG-5 apura, MG-6 sincroniza. Faltava o óbvio: ninguém tinha escrito quem escreve. Não havia como criar bolão, gerar convite, entrar por ele, sair ou trocar de papel. Não é falha de nenhuma story — é a costura entre elas, e apareceu na primeira vez que alguém tentou usar o modelo para alguma coisa. As tasks entraram como BE-1 a BE-4 desta story. Callable para participação, escrita direta para palpite Cada operação de participação escreve em mais de um lugar de uma vez (bolão + vínculo + índice) e depende de dado que o cliente não pode ver: o hash do convite, os jogos da fonte oficial. Rule não faz transação nem busca na internet. O palpite ficou de fora de propósito: é o caminho quente, e Function no meio somaria cold start ao momento em que todo mundo palpita junto. O índice da tela inicial virou gatilho Entrada, saída, papel, encerramento e arquivamento são cinco portas para o mesmo estado. Se cada callable tivesse de lembrar de atualizar o índice, a primeira que esquecesse sumiria com o bolão da tela inicial de alguém — sem erro, e só reclamando é que se descobre. A contagem de ativos é recontada, não incrementada: increment erra em silêncio quando o gatilho roda duas vezes, e o Firestore garante at-least-once. MG-7-21 mudou de alvo ao ser escrito Ele ia verificar "o último admin não sai antes de promover outro" e não há como montar o cenário: o criador é admin permanente, não sai, não é removido e não é rebaixado, então sempre sobra um. O caso ficou registrando que a guarda é rede, e não caminho. Integridade que o Postgres dava de graça: o calendário é global, então criar bolão escreve só o jogo que falta (sobrescrever apagaria o placar da sincronização e a apuração de outro bolão); e a entrada fecha no início do primeiro jogo recalculado a cada checagem, porque jogo adiado muda o horário e um valor congelado manteria a entrada fechada por um jogo que não vai mais acontecer. FE-1 a FE-6 e QA-2 seguem abertas: a camada de dados do app. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
132 casos verdes em 11 arquivos. O adaptador da fonte real veio portado com os seus 13 casos, sem alterar nenhum: cota de 10 req/min, cache de 5 min, payload malformado, timeout, 403 e 429. A trava em processo deixou de existir O defeito de 07/08 (PP-10-11) foi resolvido no NestJS com um Map em memória. Em Functions isso não vale: cada invocação é um processo novo e duas podem estar em máquinas diferentes. A trava virou documento tomado em transação — o único jeito de duas máquinas concordarem sobre quem chegou primeiro — com validade, porque Function que morre no meio deixaria o lock preso e a sincronização pararia em silêncio, que é o modo de falha que esta story existe para eliminar. Meu teste afirmava o mecanismo quando o que importa é o invariante Ele exigia "uma das duas é ignorada" e falhou: com a fonte instantânea, a primeira termina e solta o lock antes de a segunda tentar de novo, então as duas rodam em sequência — e isso é legítimo, a segunda lê o estado novo e não acha nada para mudar. Ficaram os dois casos: o invariante (uma alteração de placar no total) e o mecanismo (com fonte lenta, sobreposição real, a segunda é ignorada). Acoplamento que o teste expôs: sincronizar() dependia do app global do Admin SDK. A instância do Firestore passou a entrar por parâmetro. Decisão registrada: a sincronização não apura. Escreve o jogo, e os gatilhos de MG-5 e MG-3 reagem. Se ela apurasse, existiriam dois caminhos para a mesma regra e um envelheceria — e a correção manual do admin, que entra pela mesma porta, teria comportamento diferente. BE-3 (lembrete) e BE-4 (push) seguem abertas, e com elas FE-* e OPS-*: push depende de app registrado no projeto real, que é MG-7. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
110 casos verdes em 9 arquivos. A regra de pontuação e a de classificação foram PORTADAS do NestJS, não reescritas — as duas já eram funções puras, com 33 casos cobrindo faixa de saldo, critério exibido e posição compartilhada, e vieram com os mesmos IDs (PR-1-*, PR-5-*). Reescrever "melhor" durante a migração seria trocar regra verificada por regra nova sem ninguém pedir, e a forma de descobrir o erro seria o primeiro bolão real. Com o porte, caso que muda de resultado é defeito de migração. Preservadas com o comentário que explica por quê: empate sem faixa de saldo (1x1 num 4x4 vale 3), critério de desempate olhado para os dois lados (quem está na frente também precisa da explicação) e campeão precisa ter pontuado. Um defeito no produto e um no teste, na mesma falha - Produto: o gatilho dispara em toda escrita do jogo, inclusive o sourceUpdatedAt que a sincronização grava sem mudança — e reapurava o bolão inteiro a cada uma. Não errava o resultado; jogava leitura e escrita fora, no serviço em que custo é por operação e o free tier é requisito. Agora compara resultado antes/depois e sai fora se nada mudou, o que também é a proteção de at-least-once. - Teste: minha condição de espera ("existem 4 linhas de classificação") já era satisfeita pela passada anterior à apuração, então o teste lia zero e acusava o produto. Segunda vez nesta migração que espera mal escolhida faz teste de integração parecer defeito de regra. Decisão registrada: classificação recalculada do zero, não por delta. Mais escrita, e mantém o agregado derivável — delta acumulado erra em silêncio até alguém comparar com o extrato. BE-4 (extrato), BE-5 (correção manual) e BE-6 (aviso consolidado) seguem abertas: dependem de rota chamável pelo app, assunto de MG-7. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
Igor Matheus authored
Perfil criado por gatilho do Auth, não pelo app: se dependesse do app, uma falha de rede logo depois do cadastro deixaria a pessoa sem nome no ranking. Conta sem displayName cai para o trecho antes do @ — gravar vazio deixaria uma linha de ranking que ninguém identifica. Sondei o Auth em vez de assumir, e o resultado muda o que falta fazer -
🔴 O Auth DISTINGUE senha errada de e-mail inexistente (auth/wrong-password vs auth/user-not-found). Isso quebra AC-2-02, que existe para o login não virar oráculo de enumeração: com respostas diferentes dá para descobrir quem tem conta testando e-mails. A correção é configuração (email enumeration protection) e NÃO é verificável em emulador — o emulador não implementa. Virou a primeira coisa da lista de MG-8 e pergunta aberta no PRD. - O mínimo de senha do Auth é 6, não 8. O app valida 8 antes de chamar, mas quem falar direto com a API cria com 6. Fechar exige Identity Platform ou blocking function — upgrade de plano, decisão do dono. Registrado com o número real. - Rate limit não é verificável aqui: o emulador não aplica. AC-2-03/AC-2-04 passam a depender do comportamento em produção, para ser medido. Dois defeitos meus, nos testes - Teste que dependia de outro teste: MG-4-04 usava a conta de MG-4-01 e passava só com a suíte inteira, na ordem. Rodado isolado passava por engano — e isolado é justamente como se investiga falha. Virou auto-contido. - Asserção por mensagem em inglês (/already exists/ contra "already in use"). Trocada pelo código do erro: texto do fornecedor muda sem avisar, código não. FE-1 a FE-3 e BE-3 seguem abertas: trocar o login para o SDK mexe na camada de dados do app (assunto de MG-7) e o e-mail de recuperação exige o projeto real. Marcar agora seria dizer que o participante já entra pelo Firebase. Premissa registrada: AC-4 volta do cancelamento, porque o Auth dá recuperação de senha de graça. Se a decisão for o contrário, é texto de tela e estado de story. Co-Authored-By:Claude Opus 5 (1M context) <noreply@anthropic.com>
-
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
-