Files
gregandClaude Opus 5.5 f1271341ef
Validate game templates / validate (push) Successful in 36s
Validate game templates / boot-test (push) Successful in 1m11s
Validate game templates / publish (push) Skipped
feat(builder): repair existing templates, a weekly check that queues repairs, and eval
The builder could only create games; nothing noticed when a live template
stopped working, and there was no way to tell whether a builder change made
it better or worse.

- repair -id ID: boots an existing template as nodes run it; if it fails,
  the same investigation as for a draft runs and the model changes only what
  the diagnosis says. The worker handles "repair" jobs (gameId from the
  panel) and opens "fix(games): repair ..." on draft/<id>; a template that
  boots as it is reports "nothing to repair" and opens no PR. The boot and
  fix loop is now shared by builds and repairs.
- validate.yml runs weekly and boot-tests every game; failed games are sent
  to the panel (X-GBR-Event: games_boot_failed) to queue repairs.
- eval -ids all: builds games that already have a template from scratch,
  blind, and scores each draft against it (boots, same image and app, ports,
  ready line, settings found) into eval.md and eval.json.
- research skips our own sites, so neither research nor eval reads our
  templates back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 00:42:34 -04:00

145 lines
5.9 KiB
YAML

name: Validate game templates
# Push-only on purpose: this repo is public, and a pull_request trigger would run a fork's
# version of this workflow on our self-hosted runners. PRs from branches in this repo are
# still checked through their push.
on:
push:
# Weekly: boot-test every game, so a template that stopped working (a game update, a changed image)
# is found before players are, and a repair is queued for the AI builder
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:
inputs:
games:
description: "Games to boot-test: comma-separated ids, or 'all'"
required: true
default: all
env:
PANEL_WEBHOOK_URL: https://api.gbrhosting.com/api/v1/webhooks/gitea
jobs:
validate:
runs-on: ubuntu-latest
outputs:
games: ${{ steps.changed.outputs.games }}
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
# Three small modules aren't worth caching, and the runners' cache server is unreachable
# from job containers: each restore/save attempt timed out after ~4.7 minutes.
cache: false
- name: Validate
run: |
go test ./...
go run ./cmd/gbrgames validate
# main: games changed since the previous main commit. Branches: games changed against main,
# so results show up before merging. Manual runs pick their own.
- name: Find changed games
id: changed
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
games=$(scripts/changed-games.sh "${{ inputs.games }}")
elif [ "${{ github.event_name }}" = "schedule" ]; then
games=$(scripts/changed-games.sh all)
elif [ "${{ github.ref }}" = "refs/heads/main" ]; then
before="${{ github.event.before }}"
if [ -z "$before" ] || ! git cat-file -e "$before" 2>/dev/null; then
games=$(scripts/changed-games.sh all)
else
games=$(scripts/changed-games.sh "$before" "${{ github.sha }}")
fi
else
games=$(scripts/changed-games.sh "$(git merge-base origin/main HEAD)" HEAD)
fi
echo "Games to boot-test: ${games:-none}"
echo "games=$(echo $games)" >> "$GITHUB_OUTPUT"
boot-test:
needs: validate
if: needs.validate.outputs.games != ''
# A dedicated runner whose game containers can reach the internet but not private networks
runs-on: game-test
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
# Three small modules aren't worth caching, and the runners' cache server is unreachable
# from job containers: each restore/save attempt timed out after ~4.7 minutes.
cache: false
# Secrets named in a game's test.yaml must be listed here to reach the boot test
- name: Boot test changed games
id: boottest
env:
DRAGONWILDS_OWNER_ID: ${{ secrets.DRAGONWILDS_OWNER_ID }}
run: scripts/boot-test.sh ${{ needs.validate.outputs.games }}
# The weekly run: each game that no longer boots gets a repair job in the panel; the AI builder takes
# it, and opens a pull request with the fix
- name: Queue repairs for games that stopped booting
if: failure() && github.event_name == 'schedule' && steps.boottest.outputs.failed != ''
env:
PANEL_WEBHOOK_SECRET: ${{ secrets.PANEL_WEBHOOK_SECRET }}
run: |
games=$(printf '"%s",' ${{ steps.boottest.outputs.failed }})
curl -sS --fail-with-body --max-time 30 --retry 3 --retry-delay 10 \
-X POST "${{ env.PANEL_WEBHOOK_URL }}" \
-H "Content-Type: application/json" \
-H "X-Gitea-Token: $PANEL_WEBHOOK_SECRET" \
-H "X-GBR-Event: games_boot_failed" \
-d "{\"games\":[${games%,}],\"run\":\"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}"
# main is the source of truth: send every template to the panel, which saves the changed ones
# (new games arrive disabled). Runs only if the changed games booted, or none changed.
publish:
needs: [validate, boot-test]
if: >-
always() && github.ref == 'refs/heads/main' && github.event_name == 'push'
&& needs.validate.result == 'success'
&& (needs.boot-test.result == 'success' || needs.boot-test.result == 'skipped')
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
# Three small modules aren't worth caching, and the runners' cache server is unreachable
# from job containers: each restore/save attempt timed out after ~4.7 minutes.
cache: false
# Unlike the node-agent notify, a failure here fails the run, because the panel would
# otherwise keep serving the previous templates silently.
- name: Publish to Control Panel
env:
PANEL_WEBHOOK_SECRET: ${{ secrets.PANEL_WEBHOOK_SECRET }}
run: |
if [ -z "$PANEL_WEBHOOK_SECRET" ]; then
echo "::error::PANEL_WEBHOOK_SECRET is not set; templates were not published"
exit 1
fi
go run ./cmd/gbrgames export -repo "${{ github.repository }}" -commit "${{ github.sha }}" -out payload.json
curl -sS --fail-with-body --max-time 30 --retry 3 --retry-delay 10 \
-X POST "${{ env.PANEL_WEBHOOK_URL }}" \
-H "Content-Type: application/json" \
-H "X-Gitea-Token: $PANEL_WEBHOOK_SECRET" \
-H "X-GBR-Event: games_published" \
--data-binary @payload.json