Governança que não vira check no PR é só recomendação
Recentemente parei para olhar o histórico de um repositório que criamos há pouco mais de dois anos para controlar as bibliotecas usadas nas aplicações, e achei que valia a pena contar como foi viver isso, porque o resultado foi bem diferente do que eu esperava.
A gente sempre teve um playbook, e nele existia uma parte sobre bibliotecas, antes de adicionar uma dependência avalie se ela é mantida, se a comunidade é ativa, se não existe outra opção. Tudo fazia sentido, mas na prática quase ninguém consultava, e não era por má vontade, o problema é que uma regra que vive em um documento depende de alguém lembrar que ela existe, de alguém revisar o PR pensando nela e de alguém se sentir confortável em barrar o PR de um colega por causa dela. Com dezenas de serviços e várias squads, isso dificilmente acontece.
Por que precisávamos disso
O primeiro motivo foi a ISO. Em um processo de certificação aparecem perguntas que parecem simples, quais bibliotecas de terceiros vocês usam? quem aprovou? por qual motivo? e a gente não tinha uma resposta melhor do que “está no .csproj de cada serviço”.
O segundo foi vulnerabilidade. Quando sai uma CVE a primeira pergunta é se usamos aquela biblioteca, e sem um inventário a única forma de responder era olhar repositório por repositório. Saber o que não usamos também ajuda muito nessa hora.
O terceiro foi a saúde das aplicações a longo prazo. Cada dependência precisa acompanhar as atualizações do framework, receber correções e continuar existindo daqui alguns anos, e muitas vezes a biblioteca entrou para fazer algo básico que o próprio framework já resolvia. A ideia era forçar essa reflexão, eu preciso mesmo de uma lib para isso?
Como funciona
A solução foi bem simples. Criamos um repositório com dois arquivos, um para .NET e outro para Python, com a lista de bibliotecas aprovadas, e um check reutilizável que roda nos PRs das aplicações. Esse check lê o diff dos arquivos .csproj e pyproject.toml e, se encontrar um pacote que não está na lista, o PR falha com a mensagem:
Unapproved libraries: X. Do you really need the above library? If so, open a PR describing the reason for using.
Para liberar uma biblioteca, qualquer pessoa pode abrir um PR nesse repositório respondendo algumas perguntas do template:
- Que problema queremos resolver?
- Já temos uma forma de resolver? Se sim, por que mudar?
- A lib é mantida?
- Quantas estrelas e issues o repositório tem?
- O projeto tem testes?
O time de plataforma revisa e aprova.
A reação dos times
Eu esperava resistência, normalmente qualquer bloqueio novo é visto como burocracia, mas não foi o que aconteceu.
Acredito que o principal motivo foi que qualquer pessoa podia abrir o PR. Mesmo com a aprovação ficando com o time de plataforma, quem precisava da biblioteca não dependia de pedir para alguém, era só abrir o PR explicando o motivo, no mesmo fluxo que já usava para qualquer outro código, e a resposta ficava registrada ali. No fim, passava mais uma sensação de liberdade do que de controle, não burocratizou, apenas organizou.
Os números
Olhando o histórico do repositório até hoje:
- 92 PRs de liberação, abertos por 38 pessoas diferentes;
- 174 bibliotecas .NET e 231 Python aprovadas;
- 11 pedidos fechados sem merge.
O que mais me chamou atenção foi o comportamento ao longo do tempo. Nos três primeiros meses foram 34 PRs, quase todos de times liberando bibliotecas que já estavam em produção, muitos durante upgrade de .NET. Foi nesse momento que conseguimos enxergar o que realmente usávamos. Nos dois meses seguintes ainda vieram alguns pedidos dessa fase e, depois disso, a média ficou em 2 PRs por mês, nunca passando de 5.

Os pedidos que não foram aprovados
Os PRs recusados acabaram sendo a parte mais interessante, porque mostram as conversas que antes não aconteciam.
Em um deles, um time pediu para adicionar o client do RabbitMQ diretamente no projeto. Na revisão surgiu a pergunta, a biblioteca de mensageria que vocês já usam traz esse client como dependência, por que referenciar direto? Se as versões ficarem diferentes, pode quebrar. A pessoa removeu a referência e fechou o PR.
Em outro, o pedido era uma biblioteca para debugar aplicações rodando em container. A pergunta foi se aquilo era realmente necessário, já que dava para debugar pelo VS Code, e se a biblioteca precisaria ser importada no código de produção. A conversa continuou na task do time e a biblioteca não entrou na lista.
Teve também um PR tentando liberar um SDK de storage que já tinha sido descontinuado pelo próprio fornecedor, junto com um pacote de scaffolding da IDE, e a resposta foi que essas não entrariam. A lista também serve para deixar claro o que não queremos espalhar pelas aplicações.
E teve um caso que mostrou bem o valor do histórico, um pedido foi recusado com a resposta “já discutimos essa lib no PR #14, não vamos seguir com ela”. Sem esse registro, a mesma discussão teria acontecido de novo, com outra pessoa.
Nem tudo funcionou perfeitamente, em um dos pedidos a justificativa da recusa ficou em uma thread do Slack e não no PR, então quem olhar o histórico hoje não vai entender o motivo. É justamente o que o processo tenta evitar.
Os problemas do check
O check é simples de propósito e isso trouxe algumas limitações que fomos descobrindo com o uso:
- A aprovação é pelo nome da biblioteca, vale para qualquer versão e qualquer projeto, não tem como aprovar “só para testes” ou “só no serviço X”;
- Ele olha o diff do PR e não a árvore de dependências, então um simples bump de versão também dispara o check, e uma dependência transitiva passa sem ninguém perceber;
- No Python, algumas chaves do pyproject.toml eram confundidas com bibliotecas, e foi necessário criar uma lista de nomes reservados;
- Quando o .NET passou a auditar dependências transitivas, começaram a aparecer pedidos para adicionar bibliotecas só para fixar versão. A decisão foi não aprovar, corrigir o pacote que traz a dependência e, enquanto isso, suprimir o alerta específico no projeto.
Nenhum desses pontos fez a gente desistir do check, eles foram virando regras documentadas no próprio repositório.
O mesmo modelo em outros lugares
Depois que funcionou para bibliotecas, acabamos repetindo o mesmo modelo, uma lista, um check no PR e um PR para pedir exceção, em outros lugares. Hoje temos um dicionário de campos de log, onde um linter roda no PR e valida os campos usados, e um campo novo precisa ser adicionado no dicionário por PR. Também temos a validação que impede abrir PR sem o link da task.
Com o uso de IA no dia a dia isso ficou ainda mais importante. O agente não leu o playbook e adiciona uma biblioteca sem pensar muito, mas ele passa pelo mesmo check que qualquer pessoa.
Para o que não dá para bloquear, a saída foi medir, e hoje temos uma nota de qualidade por serviço, mas isso fica para um próximo post.
Conclusão
O conhecimento sempre esteve no playbook, o que mudou foi o momento em que a regra aparece. Antes ela estava em um documento que quase ninguém abria, agora ela aparece no PR, quando ainda é fácil discutir e mudar a decisão.
Se eu fosse começar de novo, faria praticamente igual: escolheria uma regra que já causa problema, começaria liberando tudo o que já está em uso para não travar ninguém, deixaria qualquer pessoa abrir o pedido e garantiria que alguém responda rápido, porque um check sem ninguém para revisar vira gargalo e as pessoas começam a procurar um jeito de contornar.
PS: o check tem limitações, como comentei acima, mas um check simples que todo mundo usa trouxe muito mais resultado do que uma regra completa que ninguém lia.