Antes de abrir um PR no catálogo de plugins, veja o que o gate dele realmente verifica


Você escreve um plugin, abre o PR que o catálogo pede, e a CI volta com uma linha: o sha fixado não é um commit object no repositório. Você encara seu YAML — você copiou aquele sha direto da página do GitHub. Você copiou o sha de um tag object, e o gate quer o sha de um commit object. No seu PR os dois parecem idênticos.

O catálogo de plugins é um lugar onde outras pessoas executam código nas próprias máquinas, então o gate dele não é cerimônia — é uma verificação real de supply chain. Aqui está o que ele verifica, por que se importa com algo tão inocente quanto uma renomeação, e o que confirmar antes de enviar.

Onde o gate fica e quando ele roda

A admissão é um workflow dedicado que só dispara em PRs que tocam plugin-catalog/**, então ele nunca pode ficar vermelho por trabalho não relacionado. Ele tem duas tarefas:

  • structural — uma verificação de schema barata que não precisa de uma instalação do Hermes: ela valida todo o diretório plugin-catalog/, preservando o invariante de que tudo nele permanece válido.
  • pinned-source-validate — o gate de supply chain propriamente dito: o sha que você fixou precisa ser alcançável no seu repositório, e o plugin nesse commit exato precisa passar no hermes plugins validate.

Em outras palavras, o gate clona seu repositório de verdade, faz checkout do commit que você declarou e roda a validação ali. Ele não lê seu YAML e acredita na sua palavra — ele lê o código naquele commit.

Três campos são verificados um a um

Cada campo da sua entrada que um PR pode controlar é verificado antes de ser usado:

Campo O que o gate exige
repo uma URL https sem espaços em branco
sha 40 caracteres hexadecimais que o git cat-file -t reporta como um commit object
subdir um caminho relativo simples que, após a resolução, ainda resolve dentro do clone fixado

Essa verificação de tipo do sha é de onde vem o erro lá do início: um tag object é um id de objeto perfeitamente legal, mas ele se descasca para um commit que o revisor nunca declarou como o pin, então o gate o recusa. A verificação do subdir funciona do mesmo jeito — não é uma comparação literal, mas uma resolução real de caminho seguida de um teste de confinamento, que é o que torna o .. inútil como fuga.

Por que ele se importa com uma renomeação

A lista de entradas alteradas usava --diff-filter=AM — apenas adicionadas e modificadas. A pegadinha: um arquivo de entrada renomeado nunca entra nessa lista, e o gate só examina entradas que estão nela. Renomeie uma entrada perigosa e ela desliza para debaixo do gate por completo.

O filtro agora lê AMRT: adicionadas, modificadas, renomeadas e com tipo alterado (um arquivo virando um symlink, por exemplo) — todas chegam à lista. Ao lado dele há uma regra mais direta: todo symlink em qualquer lugar do clone precisa permanecer dentro do clone. Essa verificação percorre o checkout inteiro de propósito — o find não desce em um diretório que é symlink, então um link de diretório para diretório poderia esconder uma fuga que uma varredura só de diretórios de plugins nunca veria.

Garantindo que o código do plugin não possa se apoiar no gate

Esta é a parte mais interessante do design, porque o gate executa código que você não revisou (hermes plugins validate roda lógica de instalação declarada pelo plugin):

  • As entradas são lidas do fd 3, não da stdin. Código de plugin que esvazia a entrada padrão faria a lista de entradas restante sumir e pularia a validação em silêncio. Mudar para um descritor de arquivo diferente fecha essa porta.
  • O parser roda sob python3 -I, reinicia suas variáveis a cada entrada e falha fechado com um sentinela em qualquer falha de parsing.
  • PRs de entrada precisam ser apenas de dados: eles podem tocar plugin-catalog/** e contributors/emails/** e nada mais. A razão é direta — o próprio código de fiscalização do gate (o workflow, o validador, o Hermes compilado) resolve a partir do checkout do PR, então um PR de entrada que também edita ferramentas poderia neutralizar o próprio gate que o avalia.

A regra rígida demais que foi estreitada

A primeira versão da correção era ampla demais: ela exigia “apenas dados” sempre que qualquer coisa sob plugin-catalog/ mudasse. Por esse padrão 29 dos últimos 134 PRs mesclados do catálogo teriam falhado — os que carregavam mapas de e-mail de atribuição — e todo PR de README, política ou CI que tocasse o diretório também teria ficado vermelho.

A regra agora é escopada: ela roda uma vez, só quando um YAML de entrada realmente mudou, e permite contributors/emails/** junto com plugin-catalog/**. O mesmo PR também abandonou o clone de histórico completo: o diff é o commit de merge do GitHub contra seu primeiro pai, com apenas dois níveis de profundidade, e a detecção de renomeação continua funcionando.

Uma checklist antes de enviar

  • Fixe um sha de commit, não um sha de tag. Pegue o id completo de 40 caracteres na página do arquivo ou na lista de commits, não na página de Releases/Tags.
  • Mantenha o subdir como um caminho relativo simples — sem ./, sem .., sem caminhos absolutos, sem variáveis.
  • Um PR, um trabalho. Um PR de entrada do catálogo toca plugin-catalog/** (mais os mapas de atribuição) e nada mais — nem ferramentas, nem workflows, nem docs.
  • Rode o validador localmente. É o mesmo script que a CI usa: python3 scripts/validate_plugin_catalog.py plugin-catalog/.
  • Certifique-se de que o plugin passa no commit fixado. O gate clona seu repositório e faz checkout desse commit, então “a versão mais recente passa mas a fixada não” é uma causa muito comum de X vermelho.

Para entender como o catálogo funciona do ponto de vista do usuário (navegar, instalar, instalação offline), veja o guia do catálogo de plugins; os pontos de entrada no nível de comando estão na referência do hermes plugins. Se você se interessa mais pela fronteira de confiança do lado do MCP, o guia de segurança do catálogo MCP é a peça complementar.

A conclusão

A filosofia de design cabe em uma frase: tudo que um PR pode controlar é verificado antes do uso, e é verificado após a resolução, não literalmente. Renomeações, .., tag objects, symlinks, até a stdin sendo esvaziada — nada disso é “um bug no código”; tudo isso é “o checkpoint estava no lugar errado”.

Para autores de plugins, o lado bom é que as regras são estáveis. Fixe um commit object, mantenha o subdir relativo, faça uma coisa por PR — e o gate vai verificar em seu nome que o que o revisor viu e o que um usuário instala são o mesmo artefato.