Arquitectura del núcleo de BeeverForge
Ciclo de vida de un ticket
Un ticket en la columna de recogida del tablero se convierte en un job: un workflow durable que lo dimensiona, prepara una caja, hace que un agente lo implemente, ejecuta los controles propios del proyecto, hace que otro agente lo revise, abre un pull request y lo sigue hasta el merge y, donde está configurado, durante la promoción.
Estados del job#
Los estados son la enumeración JobState en beeverforge/contracts/state.py. Ese módulo solo declara los estados; no contiene ninguna tabla de transiciones. Las transiciones las realizan el ejecutor de jobs (orchestrator/machine.py), el workflow del job (runtime/temporal/workflow.py) y la ruta de promoción (orchestrator/promotion.py). El diagrama muestra las transiciones que realizan esos módulos.
- Un job también puede terminar en
donedirectamente desdemergedcuando no hay nada configurado después del merge, y desdetodocuando el dimensionamiento divide el ticket en hijos. - A
planningsolo se llega cuando el proyecto defineplanner_stage: true. - La ruta de promoción después de
mergedsolo se ejecuta cuando la promoción está habilitada para el job. De forma predeterminada, un job se detiene en el merge del pull request. - Cualquier estado de trabajo puede quedar en espera.
needs_refinementsignifica que el propio ticket necesita a una persona;failedsignifica que falló la preparación del entorno, no el ticket;blockedretiene una decisión que el agente pidió a una persona;on_holdcubre los demás impedimentos. readyaparece solo como correspondencia con una columna del tablero.ci_waitingystaging_deployingestán declarados en la enumeración y no se asignan en ningún lugar.
La tarjeta del tablero tiene su propio ciclo de vida, más pequeño, con una tabla de transiciones explícita; se describe en Principios de ingeniería.
Estaciones, de principio a fin#
1. Sondeo y cupos#
Una programación de Temporal inicia el workflow de sondeo cada 3 minutos de forma predeterminada; un ciclo que se solaparía con uno en curso se omite. Cada ciclo inicia como máximo tantos jobs como cupos libres haya: BEEVERFORGE_MAX_CONCURRENT_JOBS (predeterminado 1) menos los jobs ya en curso, en el orden del tablero. Un proyecto cuya caja necesita una certificación y no tiene una válida no se recoge; el sondeo indica el motivo.
2. Dimensionamiento y división automática#
Antes de que el job se ejecute, una pasada de dimensionamiento lee el ticket y el código (ADR-0013). Está activa de forma predeterminada. Su veredicto es uno de: cabe, poco claro o dividir. Poco claro deja el ticket en espera en needs_refinement. Dividir registra los hijos en la columna de recogida y cierra el padre; si la propia división falla, el ticket queda en espera con la propuesta para que una persona lo divida a mano. Una pasada de dimensionamiento que no puede juzgar no detiene el job: el job se ejecuta sin dimensionar y la tarjeta indica que el dimensionamiento no se ejecutó.
3. Preguntar antes de gastar#
Un proyecto puede hacer que el job reúna contexto antes de gastar una pasada de implementación y pregunte en la tarjeta cuando el contexto reunido deja una pregunta abierta (ADR-0048). Está desactivado de forma predeterminada: requiere preflight.gather: true y okf_gate: enforce. Cuando pregunta, el job termina como skipped con la nota de que preguntó antes de empezar, y una pregunta sin respuesta se recuerda una vez pasadas 48 horas.
4. Control de la especificación#
El control de la especificación es determinista. Exige un objetivo y criterios de aceptación, y rechaza un ticket cuyas listas de alcance incluido y excluido se solapan. Un rechazo deja el ticket en espera en needs_refinement con el motivo. Se ejecuta después del dimensionamiento.
5. Preparación y configuración#
En preparing, el job prepara su espacio de trabajo en la caja y ejecuta los comandos setup: del manifiesto, con un tiempo límite de 1.800 segundos. Un fallo de la configuración termina el job como failed, porque es el entorno, no el ticket, lo que necesita atención.
6. Plan y un único ejecutor#
Un único agente ejecutor planifica e implementa el ticket (ADR-0014). Una pasada de planificación separada solo se ejecuta cuando el proyecto define planner_stage: true; de forma predeterminada está desactivada.
7. Controles de validación y reparación acotada#
El job ejecuta en la caja los controles validate: declarados por el proyecto. Mientras un control bloqueante está en rojo, el agente repara, hasta repair_max_attempts (predeterminado 2, como máximo 10) y dentro del límite de costo y del presupuesto de esfuerzo del ticket (effort_budget_turns, predeterminado 400). Un job que sigue en rojo después de eso queda en espera en on_hold con su trabajo parcial en la rama.
Reparación de supresiones (ADR-0011). Cuando el diff añade una supresión como noqa, type: ignore, pragma: no cover o nosec, el job ejecuta hasta suppression_repair_max_attempts pasadas (predeterminado 1) para corregir la causa en su lugar. Una pasada que rompe un control deja el job en espera; las supresiones que quedan se muestran al revisor.
8. Revisión independiente#
Un agente revisor, en su propia sesión, recibe el ticket, el diff, los resultados de los controles y las restricciones, y nunca el relato del ejecutor sobre su trabajo. review_mode es advisory de forma predeterminada: los hallazgos se publican y no bloquean. Solo con review_mode: blocking un rechazo con hallazgos accionables provoca una pasada de reparación (review_repair_max_attempts, predeterminado 1) seguida de una revisión nueva (ADR-0006).
9. Pull request#
El job publica la rama y abre el pull request. La apertura es buscar y luego actualizar: si ya existe un pull request abierto para la rama, se reutiliza y se actualizan su título y su cuerpo, de modo que un reintento no crea un segundo. El resumen del propio agente va en el pull request, marcado como una afirmación que ningún control verifica.
10. Reparación según la CI#
Mientras el pull request está abierto, el workflow lo vigila, sea cual sea la política de merge (ADR-0004). Consulta cada 2 minutos durante la primera hora y cada 15 minutos después. Una CI en rojo provoca hasta 2 pasadas de reparación; una rama que se ha quedado atrás respecto a su base se actualiza hasta 6 veces; cuando ningún check ha informado tras 10 minutos, el job lo dice en lugar de interpretar el silencio como verde. Tras 14 días sin merge, el job queda en espera.
11. Merge sobre la base actual#
Con el merge_policy: human predeterminado, una persona hace el merge. Con merge_policy: auto, el job hace el merge solo cuando los controles están en verde, ninguna revisión bloqueante lo rechazó, no queda ninguna supresión grave, el cambio no se evalúa como de alto riesgo, no toca ninguna ruta protegida, el piso y el diff pudieron leerse y la cantidad de pruebas no disminuyó, entre las condiciones de orchestrator/merge_policy.py. Primero lleva la rama sobre la base actual y vuelve a ejecutar los controles si la base cambió (ADR-0003).
12. El cupo se libera con el merge#
El job conserva su cupo hasta que su pull request recibe el merge o, cuando la promoción está habilitada, hasta que termina la promoción; una omisión también lo libera. Terminar la pasada del agente no libera el cupo, y un job en espera lo conserva (ADR-0007, ADR-0010). Por eso el siguiente ticket parte de una base que incluye este.
13. Observación del despliegue y promoción#
Cuando el manifiesto configura post_merge_deploy, un workflow hijo independiente observa cada minuto el despliegue propio del proyecto e informa del resultado; no ocupa un cupo y no bloquea (ADR-0005). Cuando la promoción está habilitada, el job verifica staging y luego espera en awaiting_prod_approval a una persona, hasta 3 días antes de quedar en espera. La aprobación llega al workflow como una respuesta sellada que se verifica antes de actuar y que solo puede usarse una vez. Tras la aprobación, el job etiqueta la versión, verifica producción y termina en done, o revierte y queda en espera.
14. En espera con opciones#
Un job que no puede continuar queda en espera y mueve su tarjeta a la columna de atención del tablero con un comentario que explica el motivo. Las opciones ofrecidas son reanudar, que continúa el trabajo conservado, y omitir, que libera el cupo, además de las opciones de decisión que el job haya planteado. Un job en espera conserva su cupo. Un impedimento simple que nadie responde en 3 días se omite automáticamente, lo que libera el cupo para que la cola no se atasque; una espera por decisión se mantiene hasta que se responde. Un job se registra como skipped cuando una persona lo omite, cuando preguntó antes de empezar (estación 3) o cuando su tarjeta se retiró, por ejemplo cerrada como no planificada, mientras esperaba en la cola; la omisión automática lo deja en on_hold. Un job que alcanza un límite de uso pasa a paused y se reanuda solo con una espera que crece de 30 minutos a 2 horas, como máximo 48 veces, y después queda en espera.
Fuentes#
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