feat(builder): boots as nodes run them, fixed-size model requests, safety check, repairs #25

Merged
greg merged 6 commits from feat/builder-boot-fidelity-repair into main 2026-10-26 13:49:26 +00:00
Owner

The AI builder could call a template working when a node would break it, and a long build could lose its own facts. This PR fixes both, adds checks that don't depend on the model, and lets the builder repair existing templates.

A pass now means it works the way a node runs it

  • One deploy path. Every boot the builder does, first boots included, goes through the node-agent's own deploy code (BootTest). It gets exactly the mounts, user and restart behaviour nodes use. The separate Go copy of the deploy, which had drifted, is gone.
  • Every lost file counts. Any file the server writes outside /server and /data counts, not only config-like files. That includes the image's own VOLUME folders, which a node recreates empty on every start. Docker's own init file, the Steam client's own state, logs, and lock and pid files are left out.
  • Stays up, and listens on its ports. Boot tests in CI and in the builder need the server to stay up for 60 s after its ready line (holdSec in test.yaml). Once up, it must listen on every published port (expectPorts, from defaultPorts). A port that only opens when a player connects goes in the template's test.yaml as unboundPorts, with the reason. One template uses this so far.
  • Real settings. Boot tests fill only x-generate fields, as the panel does. The builder's diagnostic second boot still fills blank passwords, to tell a missing password apart from a broken install.

Steam download cache

Each Steam app's install is kept in a Docker volume (gbr-steamcache-<app>, label gbr.cache=steam). The cache is updated once per run, and each boot's server volume is seeded from it, so a repeat boot checks files instead of downloading the whole game. STEAM_CACHE=off turns it off. RUNNER.md now gives prune commands that keep the cache and the gbr-boottest network. Never use docker system prune on a runner.

Model requests stay a fixed size

Each drafting round sends:

  • the rules and the facts;
  • the last answer;
  • the current problems;
  • one line per earlier round.

It no longer re-sends the whole growing conversation, which overran the model's context in long builds. Token use is logged, with a warning near the limit. Image scripts and diagnose output are capped.

Safety and review

  • A deterministic check rejects unsafe startup and install commands:
    • downloads piped into a shell, base64-decoded scripts, eval;
    • reverse shells, sudo/su, setuid;
    • writes to /etc; cron, systemctl, iptables, mount and kernel modules;
    • miners.
  • The PR summary now separates three things:
    • what the builder measured itself;
    • the download addresses nodes will fetch (review these);
    • what the model read in its sources.
  • Our own sites are dropped from search results.

Keeping templates working

  • gbrgames repair -id <game> boots an existing template as nodes run it. If it fails, the builder diagnoses it and opens a PR with the fix. A template that boots is left alone ("nothing to repair"). The worker takes repair jobs, which the panel queues from a game's page.
  • Weekly check. CI boot-tests every game on Mondays at 06:00 UTC. For each game that fails, it posts games_boot_failed to the panel, which queues a repair. This uses the existing PANEL_WEBHOOK_SECRET.
  • gbrgames eval builds existing games blind in a temp folder and scores each draft against the real template: route, ports, ready line and settings. Use it to check builder changes.

Merge order

  1. gbreed-public/gbr-node-agent#30 first. The builder's first boots need its keep mode, and the hold and port checks only run with that agent.
  2. This PR.
  3. Then the panel and client PRs: repair jobs, the builder status, and a lighter job list.

After merging, boot-test all games (Actions → Validate game templates → Run workflow, all): boot tests now also check the hold and the ports.

Tested

  • go test ./... passes. New tests cover:

    • the lost-file filter;
    • payload ports;
    • the unsafe-command check;
    • fixed-size requests;
    • PR text, draft scoring and repair results.
  • Run locally against a node-agent built from #30:

    • a boot test with the hold and port checks;
    • an untouched first boot through the agent;
    • two Steam first boots, the second seeded from the cache.

    That run is what found the false "files lost" report from Docker's init file and the Steam client's config, fixed here.

🤖 Generated with Claude Code

The AI builder could call a template working when a node would break it, and a long build could lose its own facts. This PR fixes both, adds checks that don't depend on the model, and lets the builder repair existing templates. ### A pass now means it works the way a node runs it - **One deploy path.** Every boot the builder does, first boots included, goes through the node-agent's own deploy code (BootTest). It gets exactly the mounts, user and restart behaviour nodes use. The separate Go copy of the deploy, which had drifted, is gone. - **Every lost file counts.** Any file the server writes outside `/server` and `/data` counts, not only config-like files. That includes the image's own `VOLUME` folders, which a node recreates empty on every start. Docker's own init file, the Steam client's own state, logs, and lock and pid files are left out. - **Stays up, and listens on its ports.** Boot tests in CI and in the builder need the server to stay up for 60 s after its ready line (`holdSec` in test.yaml). Once up, it must listen on every published port (`expectPorts`, from `defaultPorts`). A port that only opens when a player connects goes in the template's test.yaml as `unboundPorts`, with the reason. One template uses this so far. - **Real settings.** Boot tests fill only `x-generate` fields, as the panel does. The builder's diagnostic second boot still fills blank passwords, to tell a missing password apart from a broken install. ### Steam download cache Each Steam app's install is kept in a Docker volume (`gbr-steamcache-<app>`, label `gbr.cache=steam`). The cache is updated once per run, and each boot's server volume is seeded from it, so a repeat boot checks files instead of downloading the whole game. `STEAM_CACHE=off` turns it off. RUNNER.md now gives prune commands that keep the cache and the `gbr-boottest` network. Never use `docker system prune` on a runner. ### Model requests stay a fixed size Each drafting round sends: - the rules and the facts; - the last answer; - the current problems; - one line per earlier round. It no longer re-sends the whole growing conversation, which overran the model's context in long builds. Token use is logged, with a warning near the limit. Image scripts and diagnose output are capped. ### Safety and review - A deterministic check rejects unsafe startup and install commands: - downloads piped into a shell, base64-decoded scripts, `eval`; - reverse shells, `sudo`/`su`, setuid; - writes to `/etc`; cron, systemctl, iptables, mount and kernel modules; - miners. - The PR summary now separates three things: - what the builder measured itself; - the download addresses nodes will fetch (review these); - what the model read in its sources. - Our own sites are dropped from search results. ### Keeping templates working - **`gbrgames repair -id <game>`** boots an existing template as nodes run it. If it fails, the builder diagnoses it and opens a PR with the fix. A template that boots is left alone ("nothing to repair"). The worker takes `repair` jobs, which the panel queues from a game's page. - **Weekly check.** CI boot-tests every game on Mondays at 06:00 UTC. For each game that fails, it posts `games_boot_failed` to the panel, which queues a repair. This uses the existing `PANEL_WEBHOOK_SECRET`. - **`gbrgames eval`** builds existing games blind in a temp folder and scores each draft against the real template: route, ports, ready line and settings. Use it to check builder changes. ### Merge order 1. gbreed-public/gbr-node-agent#30 first. The builder's first boots need its keep mode, and the hold and port checks only run with that agent. 2. This PR. 3. Then the panel and client PRs: repair jobs, the builder status, and a lighter job list. After merging, boot-test all games (Actions → Validate game templates → Run workflow, `all`): boot tests now also check the hold and the ports. ### Tested - `go test ./...` passes. New tests cover: - the lost-file filter; - payload ports; - the unsafe-command check; - fixed-size requests; - PR text, draft scoring and repair results. - Run locally against a node-agent built from #30: - a boot test with the hold and port checks; - an untouched first boot through the agent; - two Steam first boots, the second seeded from the cache. That run is what found the false "files lost" report from Docker's init file and the Steam client's config, fixed here. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
greg added 6 commits 2026-10-26 13:26:04 +00:00
What a builder boot sees is now what a node does:
- First boots (untouched, or -as-deployed with exactly a new server's
  settings) are deployed by the node-agent's BootTest in keep mode; the
  builder only watches the console and inspects what's left. The Go copy of
  the deploy (its own docker run, mounts and startup prefix) is gone. It had
  drifted: it gave the image's VOLUME folders their own volumes, which nodes
  don't, so saves there looked safe while every start loses them.
- Files written outside /server and /data are counted whatever their type
  (it only counted config-like extensions, so .sav or .db saves went
  unseen), including the image's VOLUME folders; logs, pid and lock files
  and system folders are left out.
- Boot tests ask the server to stay up holdSec (60 s, test.yaml can set it)
  after its ready line and to listen on every published container port
  (needs the node-agent's matching BootTest).
- Steam apps are downloaded once per build into a gbr.cache volume and copied
  into each boot, so later boots only check the files (STEAM_CACHE=off).
- RUNNER.md no longer suggests docker system prune, which deletes the
  gbr-boottest network when nothing is using it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some servers open a published port only once a player connects (through a
proxy, for example), so the boot test's port check can't see it after boot.
A template lists those ports as unboundPorts in its test.yaml, with the
reason. The first template that needs it lists one UDP port, until a real
client confirms how players reach it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every container the node-agent starts gets /usr/sbin/docker-init, and SteamCMD
rewrites its own config.vdf on every run. Counted as files written outside the
volumes, they made every build look like it loses saves. A folder whose only
changes were logs no longer counts either, and a folder followed by a
same-named file (worlds, worlds.bak) is no longer taken for a file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The drafter no longer grows one conversation. Each request is the rules
  and facts, the model's latest template, what is wrong now and one line per
  earlier round, so long builds can't push the facts out of the model's
  32K context (Ollama drops the oldest part silently). The image's scripts
  in the facts are capped at 24000 characters in all, and diagnosis check
  results at 16000.
- Every model call logs its prompt and answer tokens, with a warning near
  the context limit.
- unsafeCommands: a draft whose startup command or installConfig pipes a
  download into a shell, decodes base64, uses eval, raw network tools or
  /dev/tcp, sudo/su, setuid bits, writes /etc, touches cron/systemd/firewall,
  or looks like a miner goes back to the model. AUTHORING.md has the rule.
- The PR summary lists every address nodes would download from under
  "Review carefully", marks the measured checks as measured, and labels the
  research notes as the model's reading, not checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(builder): repair existing templates, a weekly check that queues repairs, and eval
Validate game templates / validate (push) Successful in 36s
Validate game templates / boot-test (push) Successful in 1m11s
Validate game templates / publish (push) Skipped
f1271341ef
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>
git_ai_bot left a comment
Member

🤖 AI Code Review

⚠️ The review feedback was empty or could not be generated.


Automated review by AI Git Bot

## 🤖 AI Code Review ⚠️ *The review feedback was empty or could not be generated.* --- *Automated review by AI Git Bot*
greg merged commit 143a074c7d into main 2026-10-26 13:49:26 +00:00
greg deleted branch feat/builder-boot-fidelity-repair 2026-10-26 13:49:26 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gbreed-public/gbr-games#25