Arquitetura do núcleo do BeeverForge
Ciclo de vida de um ticket
Um ticket na coluna de entrada do quadro se torna um job: um workflow durável que o dimensiona, prepara uma caixa, faz um agente implementá-lo, executa os controles do próprio projeto, faz outro agente revisá-lo, abre um pull request e o acompanha até o merge e, onde configurado, pela promoção.
Estados do job#
Os estados são a enumeração JobState em beeverforge/contracts/state.py. Esse módulo só declara os estados; não contém nenhuma tabela de transições. As transições são feitas pelo executor de jobs (orchestrator/machine.py), pelo workflow do job (runtime/temporal/workflow.py) e pelo caminho de promoção (orchestrator/promotion.py). O diagrama mostra as transições que esses módulos fazem.
- Um job também pode terminar em
donediretamente a partir demergedquando nada está configurado depois do merge, e a partir detodoquando o dimensionamento divide o ticket em filhos. planningsó é alcançado quando o projeto defineplanner_stage: true.- O caminho de promoção depois de
mergedsó é executado quando a promoção está habilitada para o job. Por padrão, um job para no merge do pull request. - Qualquer estado de trabalho pode ficar em espera.
needs_refinementsignifica que o próprio ticket precisa de uma pessoa;failedsignifica que a preparação do ambiente falhou, não o ticket;blockedretém uma decisão que o agente pediu a uma pessoa;on_holdcobre os demais impedimentos. readyaparece apenas como mapeamento para uma coluna do quadro.ci_waitingestaging_deployingestão declarados na enumeração e não são atribuídos em lugar nenhum.
O cartão no quadro tem seu próprio ciclo de vida, menor, com uma tabela de transições explícita; ele é descrito em Princípios de engenharia.
Estações, de ponta a ponta#
1. Consulta e vagas#
Um agendamento do Temporal inicia o workflow de consulta a cada 3 minutos por padrão; um ciclo que se sobreporia a outro em andamento é ignorado. Cada ciclo inicia no máximo tantos jobs quantas vagas livres houver: BEEVERFORGE_MAX_CONCURRENT_JOBS (padrão 1) menos os jobs já em andamento, na ordem do quadro. Um projeto cuja caixa precisa de uma prova e não tem uma válida não é recolhido; a consulta informa o motivo.
2. Dimensionamento e divisão automática#
Antes de o job ser executado, uma passada de dimensionamento lê o ticket e o código (ADR-0013). Ela vem ativada por padrão. Seu veredito é um entre: cabe, pouco claro ou dividir. Pouco claro deixa o ticket em espera em needs_refinement. Dividir registra os filhos na coluna de entrada e fecha o pai; se a própria divisão falhar, o ticket fica em espera com a proposta para uma pessoa dividir manualmente. Uma passada de dimensionamento que não consegue julgar não interrompe o job: o job é executado sem dimensionamento e o cartão informa que o dimensionamento não foi executado.
3. Perguntar antes de gastar#
Um projeto pode fazer o job reunir contexto antes de gastar uma passada de implementação e perguntar no cartão quando o contexto reunido deixa uma pergunta em aberto (ADR-0048). Isso vem desativado por padrão: exige preflight.gather: true e okf_gate: enforce. Quando pergunta, o job termina como skipped com a nota de que perguntou antes de começar, e uma pergunta sem resposta é lembrada uma vez após 48 horas.
4. Controle da especificação#
O controle da especificação é determinístico. Exige um objetivo e critérios de aceitação, e recusa um ticket cujas listas de escopo incluído e excluído se sobrepõem. Uma recusa deixa o ticket em espera em needs_refinement com o motivo. Ele é executado depois do dimensionamento.
5. Preparação e configuração#
Em preparing, o job prepara seu espaço de trabalho na caixa e executa os comandos setup: do manifesto, com tempo limite de 1.800 segundos. Uma falha de configuração encerra o job como failed, porque é o ambiente, não o ticket, que precisa de atenção.
6. Plano e um único executor#
Um único agente executor planeja e implementa o ticket (ADR-0014). Uma passada de planejamento separada só é executada quando o projeto define planner_stage: true; por padrão ela fica desativada.
7. Controles de validação e reparo limitado#
O job executa na caixa os controles validate: declarados pelo projeto. Enquanto um controle bloqueante está vermelho, o agente faz reparos, até repair_max_attempts (padrão 2, no máximo 10) e dentro do teto de custo e do orçamento de esforço do ticket (effort_budget_turns, padrão 400). Um job que continua vermelho depois disso fica em espera em on_hold com o trabalho parcial no branch.
Reparo de supressões (ADR-0011). Quando o diff adiciona uma supressão como noqa, type: ignore, pragma: no cover ou nosec, o job executa até suppression_repair_max_attempts passadas (padrão 1) para corrigir a causa. Uma passada que quebra um controle deixa o job em espera; as supressões que restam são mostradas ao revisor.
8. Revisão independente#
Um agente revisor, em sua própria sessão, recebe o ticket, o diff, os resultados dos controles e as restrições, e nunca o relato do executor sobre o próprio trabalho. review_mode é advisory por padrão: os achados são publicados e não bloqueiam. Só com review_mode: blocking uma rejeição com achados acionáveis dispara uma passada de reparo (review_repair_max_attempts, padrão 1) seguida de uma nova revisão (ADR-0006).
9. Pull request#
O job publica o branch e abre o pull request. A abertura é buscar e depois atualizar: se já existe um pull request aberto para o branch, ele é reutilizado e seu título e corpo são atualizados, de modo que uma nova tentativa não cria um segundo. O resumo do próprio agente vai para o pull request, marcado como uma afirmação que nenhum controle verifica.
10. Reparo guiado pela CI#
Enquanto o pull request está aberto, o workflow o acompanha, qualquer que seja a política de merge (ADR-0004). Ele consulta a cada 2 minutos na primeira hora e a cada 15 minutos depois disso. Uma CI vermelha dispara até 2 passadas de reparo; um branch que ficou atrás da sua base é atualizado até 6 vezes; quando nenhum check informou nada após 10 minutos, o job diz isso em vez de interpretar o silêncio como verde. Após 14 dias sem merge, o job fica em espera.
11. Merge sobre a base atual#
Com o merge_policy: human padrão, uma pessoa faz o merge. Com merge_policy: auto, o job faz o merge só quando os controles estão verdes, nenhuma revisão bloqueante o rejeitou, não resta nenhuma supressão grave, a mudança não é avaliada como de alto risco, não toca nenhum caminho protegido, o piso e o diff puderam ser lidos e a contagem de testes não diminuiu, entre as condições de orchestrator/merge_policy.py. Antes, ele leva o branch para a base atual e executa os controles de novo se a base mudou (ADR-0003).
12. A vaga é liberada no merge#
O job mantém sua vaga até o merge do seu pull request ou, quando a promoção está habilitada, até a promoção terminar; um pulo também a libera. Terminar a passada do agente não libera a vaga, e um job em espera a mantém (ADR-0007, ADR-0010). Por isso, o próximo ticket parte de uma base que inclui este.
13. Observação da implantação e promoção#
Quando o manifesto configura post_merge_deploy, um workflow filho independente observa a cada minuto a implantação do próprio projeto e informa o resultado; ele não ocupa uma vaga e não bloqueia (ADR-0005). Quando a promoção está habilitada, o job verifica o staging e depois espera em awaiting_prod_approval por uma pessoa, por até 3 dias antes de ficar em espera. A aprovação chega ao workflow como uma resposta selada que é verificada antes de qualquer ação e só pode ser usada uma vez. Após a aprovação, o job cria a tag da versão, verifica a produção e termina em done, ou reverte e fica em espera.
14. Em espera com opções#
Um job que não pode continuar fica em espera e move seu cartão para a coluna de atenção do quadro com um comentário que explica o motivo. As opções oferecidas são retomar, que continua o trabalho preservado, e pular, que libera a vaga, além das opções de decisão que o job tenha levantado. Um job em espera mantém sua vaga. Um impedimento simples que ninguém responde em 3 dias é pulado automaticamente, o que libera a vaga para que a fila não trave; uma espera por decisão se mantém até ser respondida. Um job é registrado como skipped quando uma pessoa o pula, quando perguntou antes de começar (estação 3) ou quando seu cartão foi retirado, por exemplo fechado como não planejado, enquanto esperava na fila; o pulo automático o deixa em on_hold. Um job que atinge um limite de uso passa a paused e retoma sozinho com um intervalo que cresce de 30 minutos a 2 horas, no máximo 48 vezes, e depois fica em espera.
Fontes#
beeverforge/contracts/state.pybeeverforge/orchestrator/machine.pybeeverforge/orchestrator/promotion.pybeeverforge/orchestrator/merge_policy.pybeeverforge/runtime/temporal/workflow.pybeeverforge/runtime/temporal/poller.pybeeverforge/runtime/temporal/schedule.pybeeverforge/runtime/temporal/io.pybeeverforge/contracts/manifest.pybeeverforge/adapters/forge/github.py- ADR-0003, ADR-0004, ADR-0005, ADR-0006, ADR-0007, ADR-0010, ADR-0011, ADR-0013, ADR-0014, ADR-0048