CI-001: long-running qemu CI workers (Ubuntu + FreeBSD) on the beagle wire
Now:
OPEN
Beagle needs GitHub-Actions-style per-commit CI: build + ctest on Ubuntu and on FreeBSD (the closest self-hostable stand-in for mac). The Verbs basis is closed, so CI is an Extensions script, jab ci, not a verb. The runners are two LONG-RUNNING qemu VMs, each an ssh-reachable host holding a persistent clone (warm ninja cache, amortized FreeBSD boot), named like any remote: ssh://ubuntu-ci, ssh://fbsd-ci. No Docker as architecture — Linux containers cannot run FreeBSD; a containerized localhost is at most one runner flavor later. Pull-model first; push-into blocks on GIT-022.
Input
Context
The verb basis is fixed and orthogonal (Verbs); new behaviour ships as a be/ extension run by jab (Extensions), never as a verb.
Docker only covers the Ubuntu leg (containers share the Linux kernel); FreeBSD needs a qemu/bhyve VM or real hardware — so the runner abstraction is "an ssh-reachable host", not a container framework.
Push-into a VM needs the ssh-remote receive-pack serve arm, which does not exist yet — GIT-022; local-exec receivePack landed via POST-028.
The worker loop in /meta/work (clone → cmake+ninja → ctest -j16 → report) is the template; a CI run is that loop minus the code edits.
Goals
Every trunk commit gets a green/red build+ctest verdict on Ubuntu AND FreeBSD, published on the site (html/ + feed.rss) without hand-driving.
Runners are disposable at the VM layer: a wedged VM is reverted/rebuilt from its base image like any worker worktree — no pet state.
Constraints
All bytes cross the beagle wire or ssh; NO virtio-9p/shared-mount shortcuts — a runner stays remote-shaped so real remote hosts work same.
Runner code lives in the be/ submodule (beagle-ext) and versions with it; the CI roster/spec is a greppable page in the journal tree.
qcow2 base image + backing-file overlay per runner; revert = rm overlay.
Runners are named by URIs in the roster; all URI composition/resolution via the standard library (resolve_hash.js / uri), never hand-rolled.
WIP
Design decisions
Pull model first: each VM loops be head ssh://journal?* → on change be get → build+test → append a result row; no inbound auth surface.
The VM's result log is itself a beagle tree the journal side fetches — the same wire in reverse; no scp side-channels.
Flip to push-into (be put/post ssh://ubuntu-ci) once GIT-022 lands; the loop then becomes a receive-hook around the same build script.
TODOs
1. Spec page meta/ci.mkd: runner roster (URIs), trees to build, ctest targets, result-row format (commit sha, host, status, log link).
2. Ubuntu runner image: qcow2 base with toolchain (cmake, ninja, ragel, compiler), jab bin, ssh key; staged checkpoints per CI-003.
3. FreeBSD runner image: same via pkg; PROVE jab builds+runs on FreeBSD first — the mac-substitute load-bearing assumption.
4. be/ci.js runner mode: poll head → get → cmake/ninja → ctest -j16 → write result row + captured log into the VM's result tree.
5. be/ci.js collector mode (journal side): fetch each runner's result tree, aggregate per-commit status across runners.
6. Render: a make target folding CI status into html/ + feed.rss next to the blog render; red runs get the log excerpt inline.
7. Red-run procedure: collector drafts a todo/CI/ ticket body with the log attached as evidence; the user files/approves it.
8. Push-into mode: runners accept be put/post ssh://<runner> and build on receive — blocked on GIT-022.