Arquitectura del núcleo de BeeverForge

Modelo de extensión

Cada dependencia externa del núcleo, salvo el motor, Temporal, está detrás de un adaptador, y cada adaptador lo elige un registro a partir de la configuración. Un proveedor nuevo se añade instalando un paquete que declara un punto de entrada, sin cambiar el núcleo.

Ejes#

Un eje es un tipo de cosa con la que se puede extender el núcleo. La lista publicada es AXES en beeverforge/plugins.py:

tracker  board  forge  ci  harness  box  event  metrics  channel
notifier  identity  role  session_store  token_pool  box_runner
credential  board_setup  extract  embed  preview  credential_store

Una prueba mantiene esta lista exactamente igual a lo que piden los registros, y otra comprueba que cada registro consulta el cargador de puntos de entrada.

Tipos incluidos en los ejes principales
EjeTipos incluidos
harnessclaude_code, codex, kimi, opencode
trackerlocal, github, jira, azure_devops
boardlocal, github, jira, azure_devops
forgelocal, github, azure_devops
cinone y local, github y github_actions, azure_devops y azure_pipelines; cada par designa un mismo observador
boxcontainer, worktree, fargate (solo descripción; su ejecutor es un complemento)
box_runnerNinguno
channelpanel
notifierpanel
identitylocal, oidc
metricsnull, sqlite, memory
previewcompose, none
credential_storeenv, file

El grupo de puntos de entrada#

  • Un paquete declara sus adaptadores en un único grupo de puntos de entrada, beeverforge.adapters. Cada nombre es <axis>.<kind>, por ejemplo forge.gitlab. Un nombre con cualquier otra forma se omite con una advertencia que muestra la forma esperada.
  • Carga diferida. Los registros buscan primero en su propia tabla. Los puntos de entrada se cargan una vez, en la primera búsqueda de un tipo que no está incluido, y se guardan en caché; un despliegue que solo usa tipos incluidos nunca carga ninguno. Listar los complementos instalados lee los metadatos de los paquetes sin importarlos.
  • Un complemento defectuoso se registra en el log y se ignora; los demás se siguen cargando.
  • Los incluidos ganan una colisión. Declarar forge.github no sustituye la fila incluida: responde la incluida y la colisión se registra en el log. Un complemento no puede cambiar lo que significa un tipo existente para todos los proyectos de un despliegue.
  • Un tipo desconocido provoca un rechazo que lo nombra. El registro lanza un error con el tipo, los tipos conocidos y, cuando el tipo pertenece a un paquete complementario conocido, qué paquete lo incluye. Nunca recurre a un proveedor predeterminado.
Cómo resuelve un registro un tipoUn registro busca primero en su propia tabla, y una fila incluida siempre responde. Solo para un tipo que no está incluido se cargan los puntos de entrada, una vez. Responde una fila complementaria con el nombre del eje y del tipo; si no existe, el registro rechaza y nombra el tipo.sínosínoUn registro buscaun tipo en su eje¿Incluido?Responde la fila incluidaUna fila complementariacon el mismo nombre seignora y se informaLos puntos de entradabeeverforge.adapters se carganuna vez y se guardanUn nombre mal formado se omitecon una advertencia; un complementoroto se registra y se ignora¿Una fila llamada<axis>.<kind>?Responde el constructor del complementoRechazo: el tipo, lostipos conocidos y el paqueteque lo incluye, si se conoce.Ningún proveedor predeterminado.
Cómo resuelve un registro un tipo. Primero lee su propia tabla, así que un despliegue que solo usa tipos incluidos nunca carga un punto de entrada.

Nacer con dos#

ADR-0022 fija el listón: un eje es agnóstico no porque declare una interfaz, sino cuando nace con más de una implementación y un punto de unión que despacha entre ellas. Los protocolos son @runtime_checkable y las pruebas los comprueban con isinstance.

La regla no se cumple hoy en todos los ejes. Los ejes channel, notifier, session_store y token_pool tienen un solo tipo incluido cada uno, y box_runner no tiene ninguno. Ninguna prueba exige dos tipos incluidos por eje; existen pruebas por eje para los ejes preview y credential_store.

Conformidad de adaptadores#

beeverforge conformance-adapter <kind> <factory> ejecuta un adaptador contra la suite de conformidad de la plataforma, beeverforge/conformance/adapters.py. La función fábrica (factory) es una ruta de importación, pkg.module:attr, que nombra una instancia, una clase o una función que devuelve una.

  • Las comprobaciones son funciones simples que devuelven hallazgos. No construyen nada remoto ni cambian nada remoto; ejercitan el contrato local: firmas, reglas de degradación y la forma de los rechazos.
  • Hay comprobaciones para canales, notificadores, identidad, tableros, trackers, plataformas Git, harnesses, observadores de CI, cajas y vistas previas.
  • Ejemplos: una lectura de un tracker debe devolver None cuando no pudo leer y una lista vacía cuando no hay nada; una plataforma Git debe enviar una credencial solo al host que la emitió; la comprobación de salud de un observador devuelve un booleano y nunca lanza errores.

beeverforge conformance <project> es un comando distinto: comprueba el manifiesto de un proyecto, no un adaptador.

El stack no necesita adaptador#

El núcleo no nombra ninguna herramienta de compilación. Las entradas setup: y validate: del manifiesto son comandos de shell, y la caja ejecuta cada uno tal como está escrito: sh -c en un contenedor, un shell en un worktree. Un proyecto en cualquier lenguaje se admite declarando sus propios comandos, sin adaptador.

Ejemplo: un complemento de notificación#

De docs/writing-an-addon.md. El paquete declara un punto de entrada:

[project.entry-points."beeverforge.adapters"]
"notifier.acme" = "beeverforge_acme:build_notifier"

e implementa lo que el núcleo llama:

class AcmeNotifier:
    def notify(self, *, message: str, level: str = "info",
               about: str = "") -> str | None:
        ...                      # your provider
        return None              # or the provider's own handle for the message


def build_notifier(project=None, **_kw):
    return AcmeNotifier()

Cada eje llama a su constructor con sus propios argumentos; la guía los enumera por eje y explica cómo una fila declara su nombre visible y las soluciones que el núcleo muestra para ese proveedor.

Fuentes#