Arquitetura do núcleo do BeeverForge

Princípios de engenharia

Os princípios do núcleo estão escritos no seu documento de visão e nos seus registros de decisão. Esta página os lista com o código que faz cumprir cada um e indica onde o código fica aquém do enunciado.

Princípios da visão#

De docs/core/00-vision.md. São objetivos declarados; quando o código não cumpre um deles por completo, o item diz até onde vai.

  1. O núcleo não nomeia nenhum fornecedor. Um fornecedor é alcançado por uma porta, escolhida por um registro, a partir da configuração. O teste que garante isso cobre só beeverforge/contracts e beeverforge/policy; as linhas de adaptadores incluídos nomeiam seus fornecedores, e o destino de métricas é inferido como dynamodb quando BEEVERFORGE_METRICS_TABLE está definida.
  2. Tudo o que está fora do núcleo é substituível, por alguém que não sejam os mantenedores: um ponto de entrada, não um pull request. O motor é a exceção: o Temporal não está atrás de uma porta.
  3. Uma aplicação construída sobre o núcleo não é o núcleo. Ela usa a mesma superfície que a aplicação de um terceiro usaria.
  4. O núcleo é útil por si só. O documento acrescenta que nada do que ele inclui é retirado para fabricar um caminho de upgrade; é um compromisso declarado, que o código não pode demonstrar.
  5. Um contrato é derivado do que o núcleo chama, nunca do que um provedor oferece.
  6. Um eixo é agnóstico quando nasce com duas implementações, não quando declara uma interface (ADR-0022). Vários eixos ainda não o cumprem; Modelo de extensão os lista.

Decisões do ADR-0001#

O ADR-0001 fixa o modelo conceitual. Suas decisões, por título:

  • D-1 O framework e o projeto são separados por um manifesto declarativo.
  • D-2 O conhecimento do projeto é uma cascata de autoridade: restringir, nunca afrouxar.
  • D-3 O framework é dono dos espaços; a conformidade os faz cumprir.
  • D-4 Um ambiente efêmero com um cache de dependências persistente.
  • D-5 Um worker genérico por ticket; executor e revisor são contextos separados.
  • D-6 O rótulo é um palpite; o diff é a verdade. Os componentes resolvem os stacks.
  • D-7 As dependências declaradas são a verdade; as inferidas são indicativas.
  • D-8 Um controle de qualidade da especificação: confiar para começar, verificar para terminar.
  • D-9 A documentação tem papéis padrão, não locais padrão; os ADRs são a constituição.
  • D-10 Derivar, não manter.
  • D-11 Determinístico primeiro.
  • D-12 A plataforma é um operador do ciclo de vida, não um simples abridor de pull requests.
  • D-13 Os avaliadores são chamadas estruturadas sobre controles determinísticos; o comportamento do agente é medido, não presumido.
  • D-14 Um único tenant, vários projetos.
  • D-15 Recolhimento, responsabilidade e tratamento de impedimentos.
  • D-16 Temporal como motor de execução durável.
  • D-17 Uma caixa de tarefas remota que executa o job inteiro. Neste repositório essa caixa é apenas uma descrição; seu executor é um complemento.

Invariantes, verificados contra o código#

Nunca um travamento silencioso
Toda espera no workflow do job tem um prazo ou uma pessoa no fim: o acompanhamento do merge, a aprovação de produção e um impedimento sem resposta têm prazos, e uma espera por decisão se mantém até ser respondida. As esperas trazem as opções que uma pessoa pode escolher. Um pull request em que nenhum check informa nada é sinalizado, não interpretado como verde.
Nada se perde, nada se duplica
Os jobs são workflows duráveis. Os pull requests são buscar e depois atualizar; antes de reconstruir um branch, o executor pergunta à plataforma Git se há um pull request, e se não consegue ler a resposta ele para em vez de adivinhar.
A vaga é liberada no merge
O workflow do job mantém sua vaga até o merge do pull request, até o fim da promoção quando ela está habilitada, ou até o job ser pulado. Um job em espera mantém sua vaga: um impedimento simples é pulado após 3 dias, e uma espera por decisão aguarda até ser respondida.
A qualidade é um piso
Os papéis test e security são obrigatórios antes da passada de implementação. O piso verifica que os papéis existem, não o que executam, e o controle security herdado é não bloqueante.
O código é a fonte da verdade
O mapa de conhecimento é entregue a um agente só quando suas somas de verificação provam que ele descreve a cópia de trabalho do próprio job; caso contrário, nada é entregue.
Atribuído ao bot
A fábrica faz commits e age com a identidade de bot definida pela implantação (BEEVERFORGE_BOT_NAME, BEEVERFORGE_BOT_EMAIL).
Privilégio mínimo
Em parte. O campo permissions do manifesto é aceito e marcado como inerte: nenhum código o lê, e a restrição de permissões por projeto não está implementada. O que limita uma execução é o isolamento da caixa e seu box.env.

Uma camada de ações para todos os transportes#

Tudo o que uma pessoa pode pedir à fábrica está escrito uma única vez, no catálogo de ações (ADR-0039): beeverforge/actions/, executado por actions.perform. Quem pediu é um parâmetro da chamada, não uma propriedade do transporte.

  • A API do painel serve o catálogo em POST /api/act/{name}, e suas rotas nomeadas chamam a mesma função.
  • A linha de comando o serve como beeverforge act, e vários de seus outros comandos, como adicionar ou remover um projeto e pausar o recolhimento, chamam a mesma função.
  • À conversa do tech-lead só é oferecido o que actions.proposable() retorna para a credencial de quem pergunta.

Um cartão tem um ciclo de vida e uma porta#

O estado de um cartão só muda por lifecycle.card.transition() (ADR-0055). A porta lê o registro do cartão e responde a um evento repetido com a linha já registrada; pergunta à tabela pura se o evento é permitido a partir de onde o cartão está; executa a parte do motor da decisão de uma pessoa, se houver; registra a transição no próximo número de sequência, a menos que outro escritor o tenha tomado; e depois aplica o que a tabela diz que vem em seguida.

  • As tabelas de lifecycle/table.py são puras: sem relógio, sem armazenamento, sem tracker. Os estados do cartão são backlog, todo, running, waiting_on_a_person, merged, staged, delivered, closed e removed.
  • Um teste de guarda não permite nenhum escritor de cartões fora da porta; a única exceção, por regra, é a caixa escrevendo suas marcas de progresso.
  • O ADR-0055 está implementado, embora seu registro ainda diga Proposed. Um evento, picked_up, continua indefinido e é recusado a partir de qualquer estado.
  • Os jobs não têm uma tabela equivalente. As transições dos jobs são decididas no executor e no workflow, que é a parte do ADR-0033 que continua proposta.

As recusas nomeiam a causa e a solução#

Uma recusa diz o que falhou e o que fazer. Na linha de comando, cli_refusals.py associa um erro do provedor a uma causa conhecida e imprime uma frase com a causa e sua solução, seguida das palavras do próprio provedor. Um erro que ele não reconhece mantém as palavras do provedor e nenhuma solução é inventada. Os testes exigem de cada causa sua própria solução e impedem que defeitos do próprio núcleo sejam apresentados como do provedor.

“Não foi possível ver” não é “não há nada”#

Toda leitura de um tracker segue uma regra, enunciada em sua classe base: None significa que a leitura falhou; uma lista vazia significa que a leitura deu certo e não encontrou nada. Os quadros seguem a mesma regra, e a suíte de conformidade de adaptadores a verifica para os trackers. Valores tipados como Unreadable mantêm a mesma distinção em outros pontos.

Recusar antes de escrever#

Uma verificação que pode recusar roda antes de qualquer escrita. Uma transição de cartão recusada não muda nada e diz por quê; o piso recusa um job antes da passada de implementação; o pipeline de conhecimento recusa um destino fora do seu diretório antes de clonar qualquer coisa; uma opção de linha de comando recusada informa que nada foi lido e nada foi escrito.

Um orçamento de prompt que nomeia o que deixa de fora#

O prompt de um job tem um único orçamento (ADR-0056): BEEVERFORGE_PROMPT_BUDGET, padrão de 160.000 bytes, compartilhado pelo cartão, pelo plano e pelos documentos que o projeto declara. off o desativa; um valor que não pode ser lido é recusado. Cada documento deixado de fora é nomeado no prompt, pelo caminho que o agente pode abrir e pelo tamanho, sob um título que diz ao agente para abri-lo antes de mudar o que ele rege. Um documento cortado pelo seu próprio limite de tamanho é listado com quanto dele foi incluído.

Neutralidade de fornecedores, garantida por testes#

  • tests/test_kernel_names_no_vendor.py analisa beeverforge/contracts e beeverforge/policy e falha diante de qualquer nome de fornecedor.
  • tests/test_the_core_names_no_vendor.py falha se um fornecedor de chat é nomeado fora dos comentários do núcleo.
  • tests/test_the_core_addon_ledger.py deriva cada importação de um SDK de fornecedor e mantém a lista igual ao registro em docs/core/07-extensibility.md.
  • tests/test_the_core_is_a_core.py carrega o núcleo em um subprocesso onde todo SDK de fornecedor se recusa a ser importado, e verifica que o catálogo de ações é resolvido e que o painel inicia.

A camada de conhecimento#

A camada de conhecimento é um mapa de módulos de cada repositório, gerado de forma determinística a partir da estrutura de diretórios, das árvores sintáticas do Python e da busca de padrões em fontes TypeScript, JavaScript e C#, sem chamadas a um LLM. O mesmo estado do repositório produz uma saída idêntica byte a byte.

  • Ela vem ativada por padrão (knowledge_map: true; ADR-0035) e pode ser desativada por projeto.
  • Ela é publicada no repositório de contexto do projeto em .okf/repos/<owner>--<name>/, nunca no repositório em que se trabalha. Publicar exige product.docs_repo; sem isso, a atualização não faz nada.
  • Um agente recebe o mapa só quando se prova que ele está atualizado em relação à sua própria cópia de trabalho. Ele acelera encontrar código e não substitui lê-lo.

O papel de produto#

O papel de produto é opcional. Ele é habilitado por uma seção product: na entrada do projeto no registro, que precisa nomear o repositório de documentação que guarda os requisitos do produto (ADR-0019).

Fontes#