Extreme Group · esteira de publicação de sites · 21/09/2026

Hermes: do chamado com .zip até o site no ar — e o que garante que nada quebre o que já está publicado

Um pacote de site chega por chamado. Ninguém deste time escreveu aquele código. A esteira transforma isso em URL de homologação provada ou em recusa explicada — nunca em “acho que subiu”. Este documento descreve o que ela faz, como faz, e cada trava que a segura, em camadas: o fluxo, as provas, os guardrails mecânicos e as travas de propriedade da conta.

fonte: /opt/data/hermes-pipeline · perfil default · Fase 1 (contas pessoais) · 44 regras de guardrail conferidas em runtime · 1 site de cliente no ar + este documento, publicado pela propria esteira

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

NA MÁQUINA · REVERSÍVEL 1 · Assess “pode publicar?” — não escreve nada. Veredito: APROVADO / RESSALVAS / REPROVADO 2 a 6 · Trabalho local Ler o zip em memória (nunca extrair), sanear, montar os blobs, conferir segredo e env. Nada disso tocou em rede. Recusar aqui custa 0 Todo achado fecha em recusa com a saída escrita (next[]): nada foi criado, nada subiu. PONTO DE NÃO-RETORNO 7 · GATE src/push/gate.js Um lugar só decide “pode sair daqui?” SELO criado só pelo gate REMOTO · NÃO TEM DESFAZER 8 · Repositório no GitHub 1 repositório por site, histórico completo. Marca: topic hermes-managed. 9 · Cloudflare Pages O deploy roda no runner do GitHub, nunca nesta máquina. 10 · verify — as três provas workflow concluído + deployment de produção casado por commit · canário /hermes-release.txt com o releaseId. Sem as três, não se afirma “está no ar”. 11 · report Texto pronto para o chamado, com a seção “o que NÃO foi testado” e a cobertura. Quem assina e envia é o responsável pelo time — nunca o agente. 12 · rollback — o único caminho de volta suportado Um release atrás, sem tocar no GitHub, e só vale se o canário mudar de verdade.
Figura 1 — o gate é o único ponto que separa o reversível do permanente. Depois dele, todo objeto exige o selo.

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:

1

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.

2

O deployment é deste commit

Cloudflare registrou um deployment de produção casando o commit_hash. Preview não conta.

3

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).

REGRAS POR FASE DA ESTEIRA zip 13 9 inegociáveis · 4 sobreponíveis assess 16 0 inegociáveis · 16 sobreponíveis — nenhuma recusa de avaliação é final push 7 6 inegociáveis · 1 sobreponível — a recusa mais importante do projeto: segredo prestes a virar commit (exit 13) ci 5 4 inegociáveis · 1 sobreponível — o artefato construído passa por guardrail próprio antes de virar site verify 3 3 inegociáveis — não existe “afirmar que está no ar” sem prova inegociável — não aceita override (exit 12) sobreponível — uma pessoa decide, motivo gravado
Figura 2 — saída de allRules() lida do código em 21/09/2026. A barra é a contagem real por fase.

Os mesmos guardrails em linguagem de negócio

BlocoO que a trava impede, na prática Exemplos reaisClassificaçã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:

TravaPergunta que ela responde Onde moraRecusa
Ponto de não-retornoNada com byte de terceiro sai da máquina sem passar pelo gate?src/push/gate.js:92
Selo de runtimeToda escrita remota recebeu o selo que só o gate cria? (é um WeakSet: forjar não é possível)src/push/gate.js:70-82exit 1 (bug nosso)
Porta declarada para operação própriaRollback e rotação escrevem remotamente, mas com motivo registrado?src/push/gate.js:58-68exit 1
Identidade GitHubO token é da conta que o perfil declara?src/core/profile.js:76,89exit 45
Identidade CloudflareIdem, do lado da Cloudflare — aborta antes do primeiro byte.src/cloudflare/client.js:95exit 45
Propriedade do repositórioEste repositório foi marcado pelo Hermes (.hermes/owner.json + topic hermes-managed)?src/github/repos.js:103-118exit 44
Repositório de outro chamadoDois chamados diferentes caíram no mesmo nome?src/github/repos.js:119-129exit 44
Repositório arquivadoO site está em somente-leitura?src/github/repos.js:75-91exit 43
Nome global *.pages.devO nome já é de outra pessoa no mundo? (o namespace é global entre todas as contas Cloudflare)src/cloudflare/projects.js:67-83exit 44
Branch de produçãoO deploy vai virar produção, ou preview silencioso?src/cloudflare/projects.js:39-51exit 33
Segredo antes do commitAlgum arquivo prestes a virar commit casa com padrão de credencial?src/push/gate.js:113-150exit 13
Recusa inegociávelTentaram 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.

FaixaDe quem éO que fazerExemplos
10-19Do pedido ou do pacoteCorrigir 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-29Do código de terceiroDevolver ao fornecedor com o erro do CI. 21 zip corrompido · 22 build falhou · 24 CI bloqueou
30-39Da nossa infraestruturaPode melhorar sozinho: tentar de novo faz sentido. 30 credencial · 32 verify falhou · 33 deploy
34 run não encontrado
40-49De estadoConferir 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.

Chamado chega com um slug O slug/domínio está na lista "protected" do perfil? SIM cloudflare.slug_protegido RECUSA INEGOCIÁVEL (exit 44) — a lista é declaração humana, não achado de varredura. proposto — ainda não está em código NÃO Já existe projeto com esse nome na conta Cloudflare? SIM Nada a fazer: segue o fluxo NÃO O projeto serve o canário do Hermes em /hermes-release.txt? SIM → é nosso NÃO cloudflare.projeto_existente RESSALVA sobreponível: exige --override com quem autorizou. proposto — não está em código O projeto tem domínio próprio (algo que não é *.pages.dev)? SIM cloudflare.dominio_proprio RESSALVA sobreponível: publicar ali troca o que está no ar. proposto — não está em código NÃO → segue Publish segue com o selo e o veredito carrega o achado. A prova de propriedade é o CANÁRIO: todo site publicado pelo Hermes serve /hermes-release.txt. Site real da empresa não tem esse arquivo. Ele já existia para provar versão (verify) — passa a provar dono também. Nada novo precisa ser criado na Cloudflare.
Figura 3 — as três camadas novas, na ordem em que decidem. Duas sobreponíveis (com motivo gravado) e uma inegociável.

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.

OndeO que prova
pareceres/<ticket>-<slug>.mdO 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.mdO registro durável dos incidentes com pacote real — o que já mordeu e como foi resolvido.
canário /hermes-release.txtNo 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 arsite-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)

PerguntaPor 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)

ItemSituaçãoVerificaçã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.