Skip to content

ci: Add two-branch testing/stable workflow with pull[bot] promotion #57

@castrojo

Description

@castrojo

Summary

Add a two-branch testing/stable workflow to finpilot, mirroring the pattern used by bluefin-lts. This gives anyone who clones the template an automatic testing pipeline: PRs land on main (builds :stable-testing images), and the pull GitHub App promotes tested changes to stable (builds :stable production images).

Motivation

Currently finpilot has a single-branch model where main builds :stable images directly. There's no way to test image changes before they become production. Bluefin-lts solves this with a two-branch model and pull[bot] for automatic promotion — this issue brings that same pattern to the template.

Design Decisions

Decision Choice Rationale
Production branch name stable Matches existing :stable tag convention
Testing branch name main Matches bluefin-lts where main is the testing branch
pull[bot] reviewers OWNER placeholder Users replace with their GitHub username during setup
Build triggers Push to both branches Immediate builds on merge, matches latest bluefin-lts (no cron)

Implementation Tasks

Task 1: Create .github/pull.yml

Create pull[bot] configuration:

version: "1"
rules:
  - base: stable
    upstream: main
    mergeMethod: hardreset
    mergeUnstable: false
    reviewers:
      - OWNER
    conflictReviewers:
      - OWNER
label: "promotion"
conflictLabel: "promotion-conflict"

This tells pull[bot] to auto-create PRs from mainstable using hardreset, and skip promotion if CI is failing (mergeUnstable: false).


Task 2: Modify .github/workflows/build.yml

Four changes needed:

2a. Update triggers:

  • Add stable branch to push and pull_request triggers
  • Remove schedule cron (push triggers replace it)
  • Add merge_group support

2b. Add env vars:

PRODUCTION_BRANCH: stable  # The branch that produces :stable images
TESTING_BRANCH: main       # The branch where PRs land, produces :stable-testing images

2c. Add "Determine image tag" step (after "Prepare environment", before "Checkout"):

- name: Determine image tag
  id: determine-tag
  env:
    REF_NAME: ${{ github.ref_name }}
    EVENT_NAME: ${{ github.event_name }}
  run: |
    # If not on the production branch, this is a testing build
    if [ "${REF_NAME}" != "${PRODUCTION_BRANCH}" ]; then
      echo "DEFAULT_TAG=${DEFAULT_TAG}-testing" >> ${GITHUB_ENV}
      echo "Building testing image: ${DEFAULT_TAG}-testing"
    else
      echo "Building production image: ${DEFAULT_TAG}"
    fi

This mirrors the bluefin-lts logic from reusable-build-image.yml line 153.

2d. Relax publish conditions:

Change the if: guards on "Login to GHCR" and "Push to GHCR" from:

if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)

To:

if: github.event_name != 'pull_request'

This allows publishing from both main and stable pushes while still blocking PR builds. Apply the same change to the commented-out cosign steps.


Task 3: Rewrite .github/SETUP_CHECKLIST.md

Update the setup checklist to include:

  • Instructions to create the stable branch
  • Instructions to install the pull GitHub App
  • Replace OWNER placeholder in .github/pull.yml
  • Workflow diagram showing the testing → promotion → stable flow
  • Deploy instructions for both :stable-testing and :stable images

Task 4: Update README.md

  • Build System section: Document dual-branch builds and pull[bot] promotion
  • Development Workflow section (5): Add table of branches/tags/purposes, step-by-step workflow, testing commands
  • Deploy section (6): Show both :stable-testing and :stable deployment commands

Task 5: Update AGENTS.md

  • Branch Strategy: main = testing (:stable-testing), stable = production (:stable), pull[bot] handles promotion
  • Repository Structure tree: Add .github/pull.yml, update build.yml description
  • Release Workflow (section 8): Document both branches, all workflows, image tags, promotion flow
  • Image Tags Reference: Split into stable branch (production) and main branch (testing) sections
  • Critical Rules 7-9: Update to reflect pull[bot] promotion instead of Release Please
  • Image Signing: Remove reference to nonexistent build-testing.yml

Task 6: Update CONTRIBUTING.md

Add an "Agent-Driven Development Workflow" section covering:

  • The 8-step workflow (branch → PR → merge → testing image → pull[bot] → approve → stable image)
  • Instructions for AI agents (always target main, use conventional commits)
  • Instructions for humans (review PRs, test images, approve promotions)

Task 7: Add Justfile comment

Add clarifying comment to line 2:

export default_tag := env("DEFAULT_TAG", "stable")  # CI overrides to "stable-testing" on main branch

Task 8: Create stable branch

After all changes are committed to main:

git checkout main
git checkout -b stable
git push origin stable
git checkout main

Task 9: Final validation

  • YAML validation on build.yml and pull.yml
  • just check passes
  • git diff main..stable shows no differences (stable is a copy of main)
  • All commits use conventional commit format

Files Changed

File Action Purpose
.github/pull.yml Create pull[bot] config for main→stable promotion
.github/workflows/build.yml Modify Dual-branch triggers, testing tag logic, relaxed publish guards
.github/SETUP_CHECKLIST.md Modify pull[bot] install and stable branch creation steps
README.md Modify Document two-branch workflow and image tags
AGENTS.md Modify Update branch strategy, release workflow, image tags
CONTRIBUTING.md Modify Add agent-driven development workflow
Justfile Modify Clarifying comment about CI tag override

Files NOT Changed

File Reason
Containerfile Same image, different tags only
.github/workflows/clean.yml Already handles all tags
.github/workflows/validate-*.yml Already trigger on PRs to main
.github/renovate.json5 Renovate PRs already target main
build/, custom/ Scripts and config are tag-agnostic

Key Technical Reference

Implementation Plan

A detailed implementation plan with exact file diffs and commit messages is available. An AI coding agent can execute this plan task-by-task using conventional commits.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions