1 Em duas frases
A CLI é a mão, a skill é o juízo. Todo limite mora em código e sai por
exit code; a skill só diz quando chamar e como ler o resultado. Um guardrail que o prompt
consegue negociar não é guardrail — e chamado urgente negocia muito bem. exit 13 não negocia.
A regra de ouro é ao contrário do instinto de engenharia: recusar é mais barato do que despublicar. O custo de uma recusa é um chamado que demora mais; o custo de publicar errado é código de terceiro no ar com o nome da empresa — e commit no GitHub não tem desfazer.
2 O caminho de um chamado, e onde fica o ponto de não-retorno
Recusa é resultado final, não obstáculo. Se o publish recusou, a saída é
corrigir o pacote ou pedir decisão a uma pessoa — nunca montar o commit na mão, usar git push
ou chamar a API direto. Contornar uma recusa por outro caminho é o defeito que a esteira existe para impedir.
3 As três provas: por que “o job ficou verde” não basta
O Cloudflare Pages responde 200 com o deployment anterior quando o novo falha. Ou seja:
status code sozinho não prova nada, e “workflow concluído” também não. São três provas independentes, todas
obrigatórias, e é o verify que as exige:
O workflow concluiu
Buscado pelo head_sha do commit — nunca pelo nome do workflow. Run demora até ~15 s para existir; lista vazia não é falha.
O deployment é deste commit
Cloudflare registrou um deployment de produção casando o commit_hash. Preview não conta.
O canário responde
GET /hermes-release.txt devolvendo exatamente o releaseId. É a prova que pega o caso cruel do Pages.
4 Os guardrails em números
São 44 regras mecânicas em cinco fases. O critério que separa as camadas é reversibilidade, não gravidade: INEGOCIÁVEL é o que não tem desfazer (22 regras) e SOBREPONÍVEL é o que precisa de uma pessoa decidindo, com o motivo gravado para sempre no release (22 regras).
allRules() lida do código em 21/09/2026. A barra é a contagem real por fase.Os mesmos guardrails em linguagem de negócio
| Bloco | O que a trava impede, na prática | Exemplos reais | Classificação |
|---|---|---|---|
| zip 13 regras |
Pacote que mente sobre si ou quebra a máquina antes de qualquer publicação: bomba de descompactação, caminho que escapa da pasta (zip-slip), link simbólico, arquivo corrompido ou criptografado, nomes de arquivo que colidem, pacote que traz junto uma credencial, e pacote que exige servidor (o que a esteira não hospeda). | zip.bomba zip.slip zip.git_com_credencial zip.exige_servidor |
9 inegociáveis 4 sobreponíveis |
| assess 16 regras |
Código de terceiro com cara de problema: formulário que envia dado para fora, biblioteca antiga vendorizada, execução dinâmica, código ofuscado, conteúdo misto (site https carregando http), script de domínio desconhecido, telemetria (LGPD), IP interno em comentário, e — o mais importante — o que não foi lido (cobertura parcial, CVE não consultado). | assess.form_externo assess.ofuscado assess.telemetria assess.nao_analisado |
16 sobreponíveis por doutrina: achado de avaliação nunca é final, por mais feio que pareça |
| push 7 regras |
O que viraria commit permanente: credencial no arquivo prestes a subir, nome de site
inválido ou já tomado, publicação sem index.html, arquivo grande demais, pacote que é um
repositório disfarçado, build que precisa de variáveis de ambiente que não chegaram. |
push.segredo (exit 13) push.slug_invalido push.sem_index push.precisa_de_env |
6 inegociáveis 1 sobreponível |
| ci 5 regras |
O que a build produziria: segredo embutido no artefato final, saída que só funciona com servidor, página inicial ausente, output com lixo, sourcemap publicado. | ci.segredo_no_artefato ci.codigo_servidor ci.output_sujo |
4 inegociáveis 1 sobreponível |
| verify 3 regras |
Afirmar que o site está no ar sem prova. Workflow, deployment de produção e canário — os três, sempre. | verify.workflow verify.deployment verify.canario |
3 inegociáveis |
E as travas que não são regra, são mecanismo
Além das 44 regras, existem guardrails estruturais — coisas que não se “avalia”: simplesmente não existe objeto que as satisfaça. Conferidas no código, arquivo e linha:
| Trava | Pergunta que ela responde | Onde mora | Recusa |
|---|---|---|---|
| Ponto de não-retorno | Nada com byte de terceiro sai da máquina sem passar pelo gate? | src/push/gate.js:92 | — |
| Selo de runtime | Toda escrita remota recebeu o selo que só o gate cria? (é um WeakSet: forjar não é possível) | src/push/gate.js:70-82 | exit 1 (bug nosso) |
| Porta declarada para operação própria | Rollback e rotação escrevem remotamente, mas com motivo registrado? | src/push/gate.js:58-68 | exit 1 |
| Identidade GitHub | O token é da conta que o perfil declara? | src/core/profile.js:76,89 | exit 45 |
| Identidade Cloudflare | Idem, do lado da Cloudflare — aborta antes do primeiro byte. | src/cloudflare/client.js:95 | exit 45 |
| Propriedade do repositório | Este repositório foi marcado pelo Hermes (.hermes/owner.json + topic hermes-managed)? | src/github/repos.js:103-118 | exit 44 |
| Repositório de outro chamado | Dois chamados diferentes caíram no mesmo nome? | src/github/repos.js:119-129 | exit 44 |
| Repositório arquivado | O site está em somente-leitura? | src/github/repos.js:75-91 | exit 43 |
Nome global *.pages.dev | O nome já é de outra pessoa no mundo? (o namespace é global entre todas as contas Cloudflare) | src/cloudflare/projects.js:67-83 | exit 44 |
| Branch de produção | O deploy vai virar produção, ou preview silencioso? | src/cloudflare/projects.js:39-51 | exit 33 |
| Segredo antes do commit | Algum arquivo prestes a virar commit casa com padrão de credencial? | src/push/gate.js:113-150 | exit 13 |
| Recusa inegociável | Tentaram sobrepor uma trava que não tem desfazer? | src/guard/registry.js:386 src/push/gate.js:95-111 | exit 12 |
De quem é a culpa: a faixa do exit code
Toda recusa sai com um número, e a faixa diz o que fazer em seguida. É isso que o time (e o agente) lê primeiro — sem precisar interpretar log.
| Faixa | De quem é | O que fazer | Exemplos |
|---|---|---|---|
| 10-19 | Do pedido ou do pacote | Corrigir o pacote, ou pedir decisão a uma pessoa. | 11 sem index · 12 override negado · 13 segredo 14 falta informação · 16 avaliação bloqueada |
| 20-29 | Do código de terceiro | Devolver ao fornecedor com o erro do CI. | 21 zip corrompido · 22 build falhou · 24 CI bloqueou |
| 30-39 | Da nossa infraestrutura | Pode melhorar sozinho: tentar de novo faz sentido. | 30 credencial · 32 verify falhou · 33 deploy 34 run não encontrado |
| 40-49 | De estado | Conferir o status antes de qualquer coisa. |
41 lock · 42 duplicata · 43 arquivado 44 nome tomado · 45 conta errada |
Um número nunca é reaproveitado com outro significado: quem lê um exit code
num log de meses atrás acha a linha dele na tabela. Nomes completos em src/cli/exitCodes.js.
5 O furo que encontramos — e as três camadas novas
Hoje o lado Cloudflare adota projeto por nome. src/cloudflare/projects.js:34-54:
se já existe um projeto Pages com o mesmo nome, ele é adotado, e os únicos filtros são
production_branch === "main" e a identidade da conta. A marca de propriedade
(hermes-managed) existe só no GitHub.
Na conta pessoal (Fase 1) isso não dói. Na conta da empresa, com domínio real no ar, dói: um chamado cujo slug colida com um projeto vivo faz o conteúdo do chamado substituir o que aquele site serve em produção — e o relatório passaria a entregar o domínio real da empresa como se fosse URL de homologação. Risco alto, e por isso a trava precisa existir antes de ligar o perfil da empresa.
6 O que a esteira NÃO faz
Esta seção é obrigatória num documento que circula: é o que protege quem assina. Ela é a mesma em todo relatório de chamado, e nada aqui se “resolve” com otimismo.
✗ Não responde o chamado
O report redige; quem assina e envia é o responsável pelo time. Erro sairia com o nome dele — e URL errada que já circulou não volta atrás.
✗ Não abre a página num navegador
Não clica em formulário, não confere tipografia, não roda Lighthouse nem auditoria de performance (não há Chrome nesta máquina). O relatório diz isso.
✗ Não faz teste de intrusão nem DAST
Não existe scanner de vulnerabilidade de aplicação rodando aqui. O que existe é análise estática do pacote, CVE de dependência quando há lockfile, e as três provas de publicação.
✗ Não aponta domínio definitivo nem DNS
A entrega é URL de homologação. Apontar domínio de cliente é passo separado, humano, fora da esteira.
✗ Não hospeda o que exige servidor
Next sem output: "export", Node rodando, banco de dados: recusa, com procedimento próprio. Não é “quase igual”.
✗ Não extrai nem executa nada do zip
Os arquivos viram blobs em memória; a build roda no runner descartável do GitHub. Isso apaga, por inexistência, a execução de código de terceiro nesta máquina.
E três ações nunca acontecem, por construção: apagar repositório (não tem a
capacidade), editar o deploy.yml de um site em produção, e usar
--override sem uma pessoa ter decidido.
7 Onde ficam as evidências
Auditar não depende de memória nem de painel: cada afirmação tem onde ser conferida depois.
| Onde | O que prova |
|---|---|
| pareceres/<ticket>-<slug>.md | O parecer de segurança do código do pacote, com data, commit e lacunas declaradas. |
| repositório do site (1 por site) | Histórico completo do que foi publicado, topic hermes-managed, hermes.site.json e o motivo de cada override — para sempre. É aqui que mora o estado: não há manifesto local para dessincronizar. |
| var/logs/ | Execução por execução: verbo, parâmetros, exit code e motivo da recusa. |
| .claude/skills/*/references/casos-reais.md | O registro durável dos incidentes com pacote real — o que já mordeu e como foi resolvido. |
| canário /hermes-release.txt | No próprio site: qual release está sendo servido agora. |
8 Fase 1 → Fase 2: o que muda ao ligar a conta da empresa
Onde estamos (Fase 1)
Contas pessoais do responsável, sem domínio de cliente. Três travas contra escrever na conta errada já
existem: asserção de identidade (exit 45, zero escrita), token amarrado ao perfil, e marcador de propriedade
no recurso. Hoje: 2 sites no ar — site-pointer (ESM-5167, release
v6-7066f776, veredito “APROVADO COM RESSALVAS”) e este documento
(DOC-0001), publicado pela própria esteira.
O que muda na Fase 2
É perfil novo, não reescrita: novo arquivo de perfil e credenciais com lead time de
PAT/SSO por conta da organização. A lista protected nasce ali, e a trava de propriedade do
projeto precisa entrar antes de o perfil apontar para a conta da empresa.
Detalhe que costuma passar batido: o nome *.pages.dev é global entre
todas as contas Cloudflare do mundo. Não existe “namespace da empresa”: se o nome está tomado por
qualquer outra pessoa, não é nosso — e a esteira recusa nomeando isso (exit 44).
9 Decisões pendentes (o que depende de gente, não de código)
| Pergunta | Por que importa |
|---|---|
A lista protected é inegociável ou sobreponível com motivo gravado? |
Doutrina da casa: só o que não tem desfazer é inegociável. A lista é declaração humana explícita — o que a coloca um degrau acima de um achado de varredura. Se for sobreponível, o caminho mais curto para liberar um nome protegido passa a existir. |
| A lista mora só no perfil local (fora do Git) ou tem cópia versionada para auditoria? | Hoje quem audita não vê por que um nome está protegido. Uma cópia versionada (sem dados de conta) resolve auditoria sem expor credencial. |
| Quem assina o documento e o plano de implantação? | Todo artefato desta esteira que sai para fora do time é assinado por uma pessoa — inclusive a decisão de ligar a Fase 2. |
10 Estado real em 21/09/2026 (o que já existe × o que é proposta)
| Item | Situação | Verificação |
|---|---|---|
| As 44 regras de guardrail e as 12 travas estruturais | em código | allRules() + grep em src/, hoje |
As três provas do verify |
em código | verify.workflow/deployment/canario |
| Guarda de propriedade do projeto Cloudflare (as 3 camadas da Figura 3) | proposta | não existe src/cloudflare/guarda.js não existe regra cloudflare.* no registry |
Lista protected no perfil |
proposta | src/core/profile.js não lê “protected” |
| Perfil da conta da empresa (Fase 2) | não iniciado | config/hermes.host.json: phase 1, perfil default |
| Suíte de testes do pipeline | 168 testes, 165 verdes | 3 falhas de convenção em .claude/skills/ (frontmatter, acento, 80 colunas) — a corrigir |
| Ambiente e credenciais nesta máquina | doctor exit 0 | ./bin/hermes doctor; status: 2 sites, todos no ar |
Como ler este documento numa apresentação: as Figuras 1, 2 e 3 respondem, na ordem, “o que a esteira faz”, “o que ela recusa e com que peso” e “o que ainda falta para a conta da empresa”. As seções 6 e 10 são as que protegem a decisão: uma diz o que a esteira não garante, a outra separa o que já está em código do que é proposta.
11 Este documento é a própria esteira funcionando
Este documento não foi publicado à mão: ele passou pela esteira que descreve, como qualquer
chamado. Chamado DOC-0001, veredito APROVADO, release
v1-9d59f3ff, URL https://hermes-guardrails-doc.pages.dev — com as três
provas conferidas: workflow concluído, deployment de produção casado por commit e o canário
/hermes-release.txt devolvendo o release. O histórico está no repositório
laercioextreme/hermes-test-hermes-guardrails-doc.
O que isso demonstra na prática: o caminho que um site de cliente percorre é o mesmo caminho que qualquer página percorre — não existe “modo administrador” que pule a varredura de segredo, o gate ou as três provas. A diferença entre este documento e o site de um cliente está no conteúdo, não no processo.