Design: runners update themselves (refs #84) c90dd8d5fc36
Jetstream §8.2: runner builds from releases through the swarm, checked as a release with their signed build receipt; off unless chosen, nearest setting wins (site, organisation, test group, runner) and a runner can refuse; test groups by percentage or chosen runners, in waves with soak and health; drain, swap, prove, roll back. For Andy's review.
- Author
- Andy McCall @andymccall <[email protected]>
- via
- agent
agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key 661a76bd6613
- When
- · 2026-10-25 23:34 UTC
- Signature
- ◈ sealed by agent:claude@andymccall
- Hash
c90dd8d5fc36bc3cc6d52a0c48b4b39becc2415c97602577838bfb850ebc826f- Depends on
- 2814f4213e7c b14aeaae683a
- Depended on by
- 3e3cc9475ab3 8aa45fa6ece5 85275e1ef3b7
- In bookmarks
- main
- In tags
- v0.4.0 v0.5.0 v0.5.1 v0.5.2 v0.5.3
- Edits
- 1 atom, 0 tree operations, 1 file
Browse the tree at this change
modified docs/jetstream.md +76 −0
| … | 294 unchanged lines |
||
| 295 | 295 | upload_mib_s = 50 |
|
| 296 | 296 | ``` |
|
| 297 | 297 | ||
| 298 | + | ### 8.2 Runners update themselves |
|
| 299 | + | ||
| 300 | + | Proposed 2026-10-25 from Andy's experience: keeping a large fleet's |
|
| 301 | + | runners up to date by hand is one of the biggest costs of running CI. |
|
| 302 | + | Runners fetch new runner builds the way they fetch images, from each other |
|
| 303 | + | through the swarm, check them as strictly as a release, and swap |
|
| 304 | + | themselves in without losing work, under a policy the organisation sets, |
|
| 305 | + | testing on a few runners before the rest. |
|
| 306 | + | ||
| 307 | + | **A runner build** is a `flow` binary from an OxygenFlow release, one per |
|
| 308 | + | target (linux-x86_64, linux-arm64, linux-riscv64; Windows and macOS when |
|
| 309 | + | those runners exist), built by Flow from the tagged state with a signed |
|
| 310 | + | receipt, as oxy's and OxygenHub's releases are. The hub serves it as a |
|
| 311 | + | blob with a chunk manifest, so the swarm (§8.1) carries it unchanged: one |
|
| 312 | + | runner of a site fetches each chunk from the hub and the rest from it. |
|
| 313 | + | ||
| 314 | + | **Off unless chosen, nearest wins.** The policy is set for the site, an |
|
| 315 | + | organisation, a test group and a runner; the nearest setting wins: |
|
| 316 | + | ||
| 317 | + | - `updates`: `off` (the default) or `on`; |
|
| 318 | + | - `channel`: `stable`, `canary`, or a pinned version (`0.3.4`); |
|
| 319 | + | - `window`: hours in which a runner may update (outside them it waits); |
|
| 320 | + | - `dry_run`: fetch and check, say what would happen, do not swap. |
|
| 321 | + | ||
| 322 | + | A runner's own `flow.toml` has the last word: `[runner.update] allow = |
|
| 323 | + | false` refuses every update whatever the hub says, so a site can hold a |
|
| 324 | + | machine back while it tests. |
|
| 325 | + | ||
| 326 | + | **Test groups and waves.** A test group is a named set of runners: chosen |
|
| 327 | + | by name, by label or by pool. A rollout is a release and its waves, in |
|
| 328 | + | order; each wave is a group and its share of it, **a percentage or chosen |
|
| 329 | + | runners**. A percentage is taken by a stable hash of each runner's id, so |
|
| 330 | + | "10% of build-farm" is the same machines every rollout, the ones the |
|
| 331 | + | organisation watches. A wave starts when the one before has been healthy |
|
| 332 | + | for its soak time (say 24 hours) or when an admin promotes it. For |
|
| 333 | + | example: canary (two chosen runners), then build-farm at 10%, then |
|
| 334 | + | build-farm at 100%, then everyone. |
|
| 335 | + | ||
| 336 | + | **Healthy** means the updated runners came back on the new version within |
|
| 337 | + | ten minutes, claim jobs, and their jobs' infrastructure errors (`lost`, |
|
| 338 | + | `error`, declines) over the soak are no worse than the same runners' on |
|
| 339 | + | the version before, over enough jobs to tell. A wave that is not healthy |
|
| 340 | + | halts the rollout, and its runners roll back. Job failures from the |
|
| 341 | + | pipelines themselves do not count. |
|
| 342 | + | ||
| 343 | + | **On the runner,** in order: |
|
| 344 | + | ||
| 345 | + | 1. The hub's answer to a claim or heartbeat names an update when the |
|
| 346 | + | policy says so: the version, its blob and manifest, its receipt. |
|
| 347 | + | 2. Fetch the blob through the swarm (hub as seed); every chunk checked |
|
| 348 | + | against the manifest from the hub. |
|
| 349 | + | 3. Check it as a release: its BLAKE3 matches the release asset the hub |
|
| 350 | + | attests; the receipt is signed by an `owned` build runner (trust |
|
| 351 | + | classes, #79) and names the tagged state; the target is this machine's. |
|
| 352 | + | A peer can only serve bytes; it cannot change what is installed. |
|
| 353 | + | 4. Stage it (`bin/flow.new`) and run its self-check (`flow.new tiers`, |
|
| 354 | + | `--version`). |
|
| 355 | + | 5. Drain with notice (`leave_within`, §11.5.2 of the spec): finish what |
|
| 356 | + | fits, hand back the rest with its checkpoints. |
|
| 357 | + | 6. Swap: `flow` → `flow.prev`, `flow.new` → `flow`, and restart. |
|
| 358 | + | 7. Prove it: the first claim reports the new version. A runner that will |
|
| 359 | + | not start, or that the hub does not hear from within ten minutes, goes |
|
| 360 | + | back to `flow.prev` (a start counter in the runner's home: three |
|
| 361 | + | failed starts after an update restore the previous build). |
|
| 362 | + | 8. The runner's page shows its version, channel, group, last update and |
|
| 363 | + | why; the audit log records every update and rollback. |
|
| 364 | + | ||
| 365 | + | **Phones** take runner updates with the app's releases (Android updates an |
|
| 366 | + | app only through its store or an install), so they follow the app. |
|
| 367 | + | ||
| 368 | + | **Order of work:** (1) the runner side, with a local source (`flow runner |
|
| 369 | + | update --from FILE`): check, stage, drain, swap, prove, roll back; (2) the |
|
| 370 | + | policy on the hub (opt in and out, channels, windows, dry run) and the |
|
| 371 | + | update in the claim answer; (3) test groups, waves and health; (4) the |
|
| 372 | + | swarm as transport; (5) Admin › Runners › Updates and `oh runner update`. |
|
| 373 | + | ||
| 298 | 374 | ## 9. Pools |
|
| 299 | 375 | ||
| 300 | 376 | A **pool** is one place runners come from. All pools report the same |
|
| … | 165 unchanged lines |