Aceitação empresarial¶
O AtipSpec separa checked (verificações estruturais/técnicas locais) de
verified (evidência confiável e aceitação humana). Sob uma política de
confiança, deliver exige verified; trust: required torna a política
obrigatória.
Aceitação local e aceitação confiável¶
Sem uma política, atipspec accept --code <code> registra o que uma pessoa
aprovou na conversa (a proposta, o resultado, o contrato; a spec e o plano
das entregas antigas) e o portão detecta edições posteriores. O registro
informa seu canal: terminal (o código foi digitado no prompt da CLI),
client-permission (o Claude Code foi configurado para perguntar à pessoa
antes do comando) ou assistant (nada perguntou). Mesmo o canal local mais
forte diz como o cliente foi configurado, não quem confirmou: isso é
detecção de desvio (drift) mais um relato honesto do caminho, não
identidade. Com uma política, o portão ignora esses registros e exige as
aprovações assinadas e vinculadas a papéis descritas abaixo, que autenticam
cada aprovador com uma chave cadastrada ou com a própria revisão do
provedor.
Exigir a política de confiança¶
A política fica fora do repositório; o repositório só pode declarar que ela
é exigida. Defina trust: required em .atipspec/config.yaml e proteja esse
arquivo com CODEOWNERS. Sem uma política (--policy ou
ATIPSPEC_TRUST_POLICY), check fica vermelho, o accept local fica
desativado exceto para o contrato do projeto, deliver recusa e run não
inicia. verify continua rodando para a iteração local; o check confiável
exige evidência atestada de qualquer forma. merge-check aplica a exigência
quando o head, o branch de destino ou a base de merge a definem, de modo que
um branch não pode removê-la para o seu próprio merge, nem escapar dela
partindo de um commit anterior a ela. Então ele bloqueia um recibo aceito
localmente, e um recibo confiável que a política não autentica (veja CI
antes do merge).
Dois modos de confiança¶
mode = "ssh" |
mode = "provider" |
|
|---|---|---|
| Aprovações humanas | assinaturas SSH destacadas de chaves cadastradas (approve), ou revisões do provedor coletadas e assinadas por uma chave de coletor (sync-approvals) |
lidas ao vivo do pull request pelo worker protegido com um token somente leitura; sem coletor |
| Evidência de CI | assinada por uma chave de coletor de CI cadastrada (attest) |
atestada com atestações de artefato do GitHub pelo workflow de evidência confiável, em um repositório separado e fixado em um commit, e verificada com um gh fixado |
| Chaves a cadastrar | uma por papel humano, uma para a CI, uma para o coletor | nenhuma |
| Exceções de risco | approve <slug> exception:Fn --expires |
não disponível: corrija o achado ou use o modo ssh |
| Política | schema = 1 |
schema = 2, [provider.roles] e [attestation] |
O modo provider é a recomendação padrão: precisa de um worker protegido com
GITHUB_TOKEN, a política fixada e o gh fixado, e de um repositório
confiável da organização que contenha o workflow de evidência, e nada mais.
O modo provider é exclusivo do GitHub. O portão relê o provedor a cada
execução e falha fechado diante de qualquer erro de API, revisão revogada,
head alterado ou digest divergente. check, deliver, report e
merge-check recebem o número do PR com --number ou ATIPSPEC_PR_NUMBER.
enterprise-init escreve policy.provider.example.toml,
github-evidence-trusted.yml e github-evidence-caller.yml. O modo SSH
continua sendo a opção estrita para organizações que precisam de
assinaturas sob a própria custódia de chaves, do GitLab ou de exceções de
risco.
Em um pull request, o GitHub executa os workflows que o pull request traz,
então um workflow do repositório candidato pode ser reescrito pela mudança
que ele atesta. Por isso a política vincula cada atestação de evidência a
quatro coisas: o repositório candidato (repository, verificado com
--repo), o pull request (refs/pull/<number>/merge, a ref que o GitHub
registra para uma execução de pull_request), o workflow confiável em um
repositório separado da mesma organização ([attestation] signer_workflow)
em um commit (signer_digest), e runners hospedados pelo GitHub
(allow_self_hosted_runners = true remove esta condição). Uma política cujo
workflow vive no repositório candidato, ou cujo commit não está fixado, não
carrega. O portão também exige que o head de cada registro seja o commit do
qual o worker fez checkout. Pull requests de forks não recebem token OIDC por
padrão, então sua evidência não carrega atestação e o portão falha fechado;
habilite a opção do GitHub "send write tokens to workflows from pull
requests" somente se você a aceitar, e nunca use pull_request_target no
workflow de evidência.
Perímetro de confiança¶
O repositório candidato é editável pelo seu autor e pelo agente de
programação. Os arquivos nele, incluindo o JSON de evidência, o texto de
revisão e os metadados de aprovação local, não podem ser sua própria
autoridade. Um worker de aceitação protegido usa uma CLI instalada e fixada,
uma política de confiança externa versionada e chaves públicas cadastradas.
Fixe o SHA-256 da política na configuração protegida
(ATIPSPEC_POLICY_SHA256).
enterprise-init gera exemplos de configuração e um guia de adoção. Mova e
configure a política fora do checkout; não use o template diretamente. O
template contém propositalmente chaves de placeholder inválidas. Os papéis
de assinatura de CI e de coletor de provedor não podem também deter papéis
de aprovação humana.
As contract_rules corporativas são avaliadas em todo o repositório, além
do contrato do projeto. Um projeto não pode dispensar uma regra corporativa
com uma decisão de arquitetura local. Atualize e aprove a política
corporativa separadamente.
A política também pode impor dois mínimos ao portão, booleanos de nível
superior ao lado de contract_rules: require_proof = true (todo critério
sob Tests: nomeia seu teste JUnit em Proof:, e a verificação tem um
Report:) e require_isolated_review = true (uma revisão cuja session:
não é isolated é um erro). Qualquer outro valor faz a política falhar ao
carregar. O portão aplica uma exigência quando a política ou o config.yaml
do projeto a define, de modo que um projeto pode acrescentá-la, mas não
retirá-la.
Papéis e assuntos de aprovação¶
| Fase | Papel | Conteúdo vinculado |
|---|---|---|
| spec | product | especificação e contrato de arquitetura |
| plan | engineering | especificação, plano e contrato de arquitetura |
| decision:DEC-nnn | engineering | documento de decisão e contrato alterado |
| exception:Fn | risk | árvore, revisão, termos de adiamento, expiração |
| acceptance | qa | árvore, revisão, termos de adiamento e artefatos de evidência |
Em uma entrega guiada, spec e plan vinculam um a um os requisitos que a
entrega lista, não o resto da spec viva da sua capacidade, cujos demais
requisitos check e merge-check mantêm como estavam (versão 2 do assunto;
veja quando a main avança). O que o branch
de integração trouxe não é mudança da entrega; sob uma política, apenas sua
chave integration nomeia esse branch, como
integration = "refs/remotes/origin/main": teams.json faz parte do
candidato. Sem a chave, nada é atribuído, e o que incorporar o branch de
destino traz conta como mudança da entrega em check. As
aprovações assinadas antes da 0.3.0, e as revisões do provedor que carregam
seu marcador, são comparadas com o assunto da versão 1 para o qual foram
feitas. Um assunto lê finais de linha CRLF como LF, e o mesmo fazem as
verificações de assinatura de aprovações e de registros de evidência e os
digests dos logs de saída dos comandos e das cópias de relatórios de teste:
um checkout com core.autocrlf (o padrão do Git for Windows) e um sem ele
dão o mesmo assunto e verificam as mesmas assinaturas.
O dono declarado da entrega não pode aprovar o próprio trabalho, seja qual
for a combinação de maiúsculas e minúsculas em que esteja escrito: um dono
Engineering é a identidade ou conta engineering. O dono é escrito como as
identidades, em letras ASCII, dígitos, _, ., @ e -: check e
approve recusam um que apenas se parece com uma identidade, com uma letra
cirílica ou um caractere invisível. Os adaptadores de provedor também
rejeitam o autor real do PR/MR. No modo ssh, spec.md é a palavra do autor,
então o aprovador de produto confirma o dono:
approve <slug> spec --owner <identity> falha a menos que seja o owner que
spec.md declara, e o assina com a aprovação. Uma aprovação de spec que não
confirma nenhum dono (assinada antes da 0.3.0), ou que confirma outro, não
conta: assine-a de novo com --owner. Mudar o dono muda o assunto da spec,
então o produto aprova de novo, e o novo dono não pode assinar as fases
posteriores. Os administradores ainda devem garantir que as chaves humanas
cadastradas não estejam acessíveis a agentes de desenvolvedor ou contas de
serviço. Uma assinatura válida prova a posse da chave, não a presença humana
nem uma revisão semântica completa.
Obtenha o assunto com approval-subject. Cada aprovador revisa o assunto
real e executa approve em uma máquina confiável com sua chave cadastrada.
O comando não cria chaves nem fabrica identidade. Aprovações de risco exigem
um timestamp ISO futuro via --expires; mudar os termos exige nova
aprovação.
A aprovação de plan e spec deve preceder a execução da evidência. Um novo digest de política, assunto alterado, signatário desconhecido, assinatura alterada ou aprovação expirada falha fechado. Para revogar um signatário SSH, remova a chave dele da política externa e distribua a nova política fixada. Isso invalida os artefatos da política anterior; colete novas aprovações/evidências sob a nova política.
GitHub e GitLab¶
Os adaptadores são somente leitura. Configure o tipo de provedor, a URL da API HTTPS, o repositório e as listas de papel para conta na política externa. Forneça GITHUB_TOKEN ou GITLAB_TOKEN somente ao worker protegido de coleta/verificação.
Para o GitHub, um humano cadastrado envia uma revisão APPROVED contendo o
marcador exato de approval-subject em uma linha separada. A revisão deve
ter como alvo o head atual. Para o GitLab, o humano posta esse marcador em
uma nota que não seja de sistema e aprova o MR; ambos devem existir e a
conta deve estar ativa e não ser um bot.
atipspec sync-approvals reset acceptance --number 42 \
--identity approval-collector --key /secure/collector-key \
--policy /secure/company.toml
O coletor assina o registro normalizado, e todo portão confiável lê o provedor novamente. Revisões revogadas ou alteradas falham. Um head desatualizado, erro de API, endpoint de aprovação não suportado ou acesso de token insuficiente falha fechado. O acesso à API de aprovação do GitLab depende da implantação/tier. Origens de API self-hosted devem ser configuradas explicitamente pelo administrador da política.
As integrações são cobertas por testes de contrato de API simulados (mocked). A conexão real e a proteção de branch devem ser configuradas e testadas na sua organização real.
Referências de API: GitHub reviews, GitLab approvals.
CI antes do merge¶
github-local.yml e gitlab-local.yml, de enterprise-init, executam
atipspec merge-check como o check do pull request: torne esse job
obrigatório no branch de destino. Ele falha enquanto uma entrega que o branch
toca está em draft ou não aceita, quando o código no head difere do código
que um recibo aceitou, quando o branch reescreve um arquivamento anterior,
altera uma spec viva fora de deliver, altera o contrato aceito sem sua
nova aceitação, ou altera código fora de .atipspec/ que nenhuma entrega do
branch alterou depois de seu commit base, como o código commitado antes de
atipspec new, a menos que exempt_paths, no config.yaml do branch de
destino, isente esses arquivos (veja a referência da
CLI). Ele também falha enquanto
.atipspec/framework/ não for o que a versão fixada instala, porque cada fase
imprime ao agente essas regras e esses workflows: execute
atipspec install --update com essa versão. Ele avisa, sem bloquear, quando o
branch relaxa uma política do config.yaml em relação ao branch de destino,
ou altera overview.md, glossary.md, uma decisão (e nomeia cada decisão
com seu status), adapter.json ou teams.json; e quando o branch altera o contrato ou uma decisão que o
afeta, o resultado mostra a aceitação do contrato e seu canal. O job faz checkout do head do
branch, não do commit de merge sintético do provedor, com o histórico
completo, e nada pode escrever no checkout antes de merge-check, que julga
o head commitado. Ele instala o verificador fixado de fora do checkout com
python -I, de modo que um pacote pip/ ou um wheel que o candidato commite
nunca é carregado, não executa nenhum comando do candidato e não precisa de
credenciais; os testes do projeto rodam em seu próprio job. O job do GitLab
faz fetch do branch de destino, para que seu trust: required se aplique. O
job do GitHub escreve o resultado no resumo do job (--format markdown): uma
tabela das entregas que o branch toca com seu estado e ticket, e depois as
verificações. Quando o agente abre o pull request a pedido do usuário, a
descrição traz o ticket e atipspec report <slug> --format markdown. No
modo local, um recibo não é autenticado: o check mostra que o branch
corresponde ao que foi aceito, não quem o aceitou, e como cada aprovação foi
registrada (seu canal), não quem a deu.
Um limite que se fecha com o provedor, não com o AtipSpec: o provedor
executa o workflow que o branch traz, então um branch pode reescrever ou
remover o check. Exija-o de fora do candidato: no GitHub, com um ruleset que
exija o workflow de um repositório protegido, ou com CODEOWNERS em
.github/workflows/ e a revisão de code owners obrigatória; no GitLab, com
uma pipeline execution policy ou um compliance pipeline. Localmente, o Claude
Code pergunta antes que suas ferramentas de edição alterem
.github/workflows/**, .gitlab-ci.yml ou suas próprias configurações (veja
as regras de permissão de
aprovação); isso não é um
controle sobre o branch.
exempt_paths no branch de destino deixa um branch alterar os arquivos que
ele cobre sem nenhuma entrega nem aceitação, também sob uma política.
Restrinja-o a arquivos que não precisam dela, como a documentação, e revise
uma mudança nele como uma mudança na CI: ele não isenta nada até ser
integrado ao branch de destino, e merge-check avisa no branch que o altera.
Quais arquivos não precisam de entrega é decisão da pessoa: as regras
instaladas dizem ao agente que só altere exempt_paths quando ela pedir.
Nenhuma aprovação o inclui no hash, então defini-lo na main não devolve a
draft a proposta de nenhuma entrega aberta; um branch que o integra verifica e
revisa de novo, como após qualquer mudança da árvore.
Uma lista de partida, que a pessoa adapta. Cada entrada é código que nenhum humano aprova em uma entrega: um branch altera os arquivos que ela cobre sem nenhuma entrega.
exempt_paths:
- "docs/**"
- "README.md"
- "CHANGELOG.md"
- "LICENSE"
- "package-lock.json"
- "uv.lock"
A documentação e os lockfiles são os candidatos habituais. Um lockfile fixa
as versões das dependências com que o código é executado: isente-o só onde
outra coisa revisa as atualizações de dependências, como uma revisão
obrigatória dos pull requests do bot de atualizações. Nunca inclua código,
testes, .github/**, .claude/** nem arquivos que o agente lê como
instruções, como AGENTS.md ou CLAUDE.md; um glob amplo como **/*.md
também os cobre.
O arquivo que adiciona o job também é código fora de .atipspec/, e nenhuma
entrega o cobre: o pull request que o adiciona falha no seu próprio
merge-check, enquanto o branch de destino ainda não tem exempt_paths,
então integre-o antes de tornar o job obrigatório. Uma mudança posterior
nesse arquivo, como um novo pin de ATIPSPEC_PACKAGE, falha do mesmo jeito:
faça-a em uma entrega ou, se a pessoa decidir que ela não precisa de uma,
adicione só esse arquivo a exempt_paths no branch de destino antes, sob os
owners que revisam a CI; .github/** deixaria um branch alterar qualquer
workflow sem entrega.
Com trust: required, esses jobs falham fechado: execute
merge-check --base <ref> --policy /secure/company.toml no worker protegido
e exija esse resultado em seu lugar. Com uma política, merge-check exige
aceitação confiável mesmo onde trust: required não está definido, de modo
que um recibo aceito localmente bloqueia. Um recibo é um arquivo no branch,
então com a política merge-check não o lê como prova: verifica de novo cada
entrega que o branch arquiva tal como deliver a encontrou. Em um clone
temporário com as refs do repositório, faz checkout do commit anterior ao
commit de arquivamento, restaura a evidência, a revisão, os termos de
adiamento e as aprovações arquivados com a entrega, e exige que seu check
confiável ali seja verified (as aprovações de produto, engenharia e QA e a
evidência atestada que deliver exige; as revisões do provedor coletadas
com sync-approvals são lidas de novo), que esse commit contenha o código do
head, e que a spec viva no head seja o que deliver escreve a partir dele,
inclusive cada requisito que a entrega lista.
Faça o commit do arquivamento sozinho, logo depois de deliver. No modo
provider, ele lê as revisões do pull request sobre esse commit, ou sobre o
novo head quando elas carregam as mesmas linhas de marcador: se o push do
arquivamento descartar revisões obsoletas, os revisores aprovam o novo head
de novo com as mesmas linhas. Sob uma política, o contrato e as decisões que
o afetam só mudam em uma entrega verificada dessa forma, contract.md com
uma nova decisão que o afeta, aceita e aprovada pela engenharia; um contrato
em draft é aceito pela primeira vez com uma entrega assim, cuja aprovação do
plano o nomeia. Um branch que os altera de outra forma bloqueia, diga o que
disser o registro local do contrato.
Execução e coleta de CI¶
Os jobs de evidência executam os comandos candidatos sem chaves de assinatura. Use o histórico completo. Proteja o pin do pacote verificador e os workflows. Um orquestrador protegido deve autenticar o workflow produtor, o repositório, o head de origem, a execução bem-sucedida e o download do artefato antes de atestar.
No modo provider, github-evidence-trusted.yml é esse orquestrador.
Copie-o para o repositório confiável; cada repositório candidato o chama no
commit fixado com github-evidence-caller.yml, somente em pull_request.
Seu job produce faz checkout do head do pull request, apaga a evidência
commitada no branch, executa os comandos Verify apenas com acesso de leitura
e envia os registros que escreveu. Seu job attest não executa nenhum
comando candidato: pega esses registros pelo ID do artefato, de modo que um
artefato que outro job da execução substituiu faz o download falhar, exige
exatamente um registro aprovado por tarefa verificada de cada entrega ativa,
escrito nesse head e nessa árvore, e atesta apenas esses. O worker protegido
também apaga a evidência commitada, baixa da última execução bem-sucedida no
head o artefato da tentativa dessa execução, atipspec-evidence-<attempt>,
copia apenas suas pastas evidence/ e executa check ou deliver com
--number, que falham diante de um registro que attest não atestou;
ADOPTION.md lista os comandos. A verificação vem depois das aprovações de
spec e plan: execute o workflow de novo no head assim que elas forem dadas,
e o QA aprova depois dele. A atestação prova onde, quando e em qual head a
evidência foi produzida, não que o código sob teste é honesto: ele roda em
produce e poderia falsificar ali seus próprios resultados, o que a revisão
e as aprovações no head cobrem.
Execute os comandos candidatos em um worker efêmero sem chaves de
assinatura. Colete e assine em um worker protegido separado, usando um
checkout novo e higienizado e python -I -m atipspec a partir de um pacote
instalado e fixado. attest assina, para cada tarefa, o registro em
evidence/ que terminou por último: apague a evidência que o branch
commitou (atipspec.verify.clear_evidence) antes que o produtor verifique e
antes que o coletor restaure seus registros. Não instale nem importe a
implementação do AtipSpec do candidato. Nunca execute comandos candidatos no
coletor. Isolar credenciais em outro passo em um worker persistente
comprometido não é suficiente.
Cada comando tem um log de saída completo cujo hash é verificado contra o registro de evidência assinado, assim como o relatório JUnit copiado quando uma tarefa nomeia um. Mantenha esses logs e relatórios junto com a evidência.
attest valida e assina registros em nome desse coletor confiável. Ele não
consulta nem autentica de forma independente a URL de execução de CI
alegada. Assinar um JSON arbitrário fornecido pelo autor derrotaria o
perímetro de confiança. A integração nativa do provedor de atestação de
artefato/orquestrador é específica de cada implantação e deve ser
configurada antes de usar o script de aceitação protegido em produção.
Exija o resultado da aceitação protegida com proteção de branch ou regras de MR. As verificações locais permanecem diagnósticas e podem reportar um blocker aguardando uma exceção de risco confiável. A CLI não pode impedir um administrador de contornar as regras do repositório.
Dossiê e piloto¶
report --format json|markdown|html exporta uma matriz de rastreabilidade,
ponteiros de prova com aquilo a que cada um foi resolvido, o revisor e a
sessão da revisão, referências de execução de CI, aprovações verificadas,
achados, informações de política/versão, e as mudanças que o QA assina:
todo arquivo alterado desde o commit base com seu status, linhas e se está
fora do escopo do plano, e o diff (até 200 KB, 50 KB em markdown, com o
tamanho e o sha256 do diff completo). O HTML escapa texto não confiável. Os
artefatos assinados são a evidência; os próprios hashes de um relatório são
um índice, não uma certificação separada. Mantenha ambos.
pilot init <name> cria um protocolo sem medições. A equipe completa
coortes, elegibilidade, janela de observação, limiares e humanos
responsáveis. pilot record exige medições reais com fonte e coletor;
pilot report fornece comparações descritivas e sinaliza janelas
incompatíveis. Alegações reais de desempenho exigem um piloto concluído;
exemplos gerados não são medições. Use uma linha de base equivalente e a
mesma janela de observação de defeitos.