Changelog
0.2.0 is OxygenFlow's first tagged release; until then the runners ran main. This file starts on 2026-10-01; earlier history is in oxy log.
0.5.3 (2026-11-04)
- Added: a
changelogcheck on the WASM tier..flow/checks.wasm changelog CHANGELOG.md [MANIFEST]checks the changelog's shape (## Unreleasedfirst if at all, then## X.Y.Z (YYYY-MM-DD)sections newest first, none empty) and that the version aCargo.toml,.propertiesorpackage.jsonsays has its section, so a release never finds its notes missing. It is a job of OxygenFlow's pipeline, and of OxygenHub's, Oxygen's and the Android app's, any runner with the WASM tier taking it. - Added:
flow deps check(#101). Jobs have no network, so every crate aCargo.lockneeds must be vendored in the build image at that version.flow deps check [Cargo.lock]says whether it is, before the push: each missing crate with the versions the image does have and thecargo update --precisethat pins one, exit 1. It reads the image's crate list (--list), a vendor directory (--vendor), or the lock files the image is built from (--image-lock, repeatable); inside a job, the image's own.flow deps listprints the set. - Changed: the build image publishes its crate list.
images/rust/build.shwritescrates.txt(NAME VERSIONa line) beside the tarball and at/opt/crates.txtin the image. - What a runner is running, for its host (oxygenhub-android#8):
flow::runner::running()lists the jobs this runner is running now (owner/name #run job), each from just before it starts until it ends, so the Android app can show them on its card and notification. flowbuilds and runs on FreeBSD (#99): FreeBSD 15.1 with pkg's Rust 1.98 buildsflowwith the WASM tier (wasmtime runs there); thenoatimecheck is Linux's only, as on Haiku. A FreeBSD machine can be a WASM runner (flow tiers: wasm available; the microvm and syscall tiers say they are Linux's).
0.5.2 (2026-10-29)
- Runner builds through the swarm (#84): a runner in a swarm fetches an offered build as the image its BLAKE3 names, from its site's peers and the hub together, as it fetches images, and checks its size and BLAKE3 before installing it; over HTTP as before when it is in no swarm, the hub has not shared the build yet, or the swarm cannot get it. The hub shares a build the first time it offers it (OxygenHub 0.9.15), so a site's runners fetch it from each other and the hub serves it about once per site. The build stays in the runner's images for its peers.
flowbuilds on Haiku (#91): with--no-default-features,flownow builds natively on Haiku R1/beta6 (HaikuPorts' Rust 1.94): the syscall tier, the guest's job code and the load average are Linux's (and Android's) only, and thenoatimecheck takes reads as recorded where statvfs has no such flag. It checks pipelines and runsflow tiers, which says no tier runs there yet: the host tier (#90) is next.- The WASM tier can be left out of a build (#91): the
wasmcargo feature (on by default) carries wasmtime;--no-default-featuresbuildsflowfor systems wasmtime does not run on, such as Haiku, whose runners then offer no WASM tier and say why.
0.5.1 (2026-10-28)
-
Sealed alerts (
flow_core::alert, oxygenhub-android#16): what a phone is told about a job is encrypted to an X25519 key the phone made (a fresh key per alert, HKDF-SHA256, ChaCha20-Poly1305), so the push service carrying it sees only ciphertext.sealfor the hub,openandnew_secret/public_keyfor the phone, from crates already in the tree (curve25519-dalek, ring). -
flow runner register --cakeeps the CA in the Flow home (#98): it wrote the path as given, so--ca hub-ca.pemleftca = "hub-ca.pem"in runner.toml and a service started elsewhere could not find it. Register now copies the file to the home'shub-ca.pemand records that copy's absolute path, and a relativecaalready in runner.toml is read against the home. -
The cheap checks run as WASM (#96):
layout,todos,sizeandsummaryare one WASI module,crates/flow-checks, built byscripts/checks-wasm.shand committed as.flow/checks.wasm, so they need no image or microVM and any runner with the WASM tier takes them (the phone, plex, windows-1); each runs in under 100 ms. The fmt job fails unless the committed module is what the source builds, and the build image has the wasm32-wasip1 target for that.
0.5.0 (2026-10-27)
- A job's report says where its time went (#35). The finish report carries
phasesin milliseconds:image(fetching its image, when the runner lacked it),prepare(its workspace and disks),run(the sandbox from start to stop) andfinish(outputs, caches and receipt), so a slow job shows whether it waited on an image, a fetch or itself. - The swarm reads images in order (#97). A fetcher took equally rare chunks at random, so a peer read its whole image in random order, a seek per chunk on a spinning disk: plex-1 got 2.3 GiB from earth-1 at about 2 MB/s. Neighbouring chunks now come in runs, shuffled so runners starting together still spread, and the picker no longer rescans what it has: picking a 142,244-chunk image went from 19 s to 0.35 s.
- The swarm moves an image nobody pushed (#94). A runner that serves its peers tells the hub, at start and every ten minutes, which images it holds, and sends the manifest of each the hub keeps none of: the manifest alone, not the image. With a hub that keeps such manifests (oxygenhub#342), runners in the same site fetch the image from it. A fetch told the hub holds no chunks asks peers alone, and fails plainly when none can serve it. Design: docs/jetstream.md §8.1.1.
- A WASM job stopped while its module compiles stops (#93). The time limit or a cancel moved the engine's epoch once; a store made after that set its deadline past it, so the job ran on forever (seen as a test spinning 35 minutes under load). Once stopped, the epoch now moves every tick until the job ends.
- A runner claims only what its host can start (#92). An idle runner told the hub it had no memory limit, so it was offered sandboxes bigger than its machine, fetched their images, and declined them one by one (mars-1, 7 GB, and the 8 GiB jobs). A claim now asks for the budget's room capped at the host's free memory.
flow rootfs pushwaits for the hub to check a big image (oxygenhub#341). The hub reads the whole image before it accepts the manifest; the manifest request now waits up to an hour, not the 120 s every other request gets.- The Android image (#19).
images/android: OpenJDK 17, the Android SDK (command-line tools 23.0, platform 37.0, build-tools 36.0.0, NDK 30.0), Gradle 9.6.0, Rust with the Android targets and cargo-ndk, and the OxygenHub app's crates and Gradle dependencies, so the app builds and tests offline. Each download is checked against its published hash. flow runner benchsays when load is not measured (#83): a Windows runner printedload 0.00; it now saysload not measured here. Its kernel line readsWindows 10.0.26300(checked with 0.4.0 in the Windows VM).
0.4.0 (2026-10-26)
- A Windows runner reports its Windows version (#83): as its kernel fact (
Windows 10.0.26100, fromRtlGetVersion), shown on the hub's runner page and inflow runner bench. Built and linted for Windows; not yet run there. flow runner drain(#83): asks the home's running runner to drain, as SIGTERM does on Linux, through adrain.requestfile its loop reads: the way to stop a Windows runner without losing its job. A starting runner clears a stale request.- A Windows runner updates itself (#83, #85):
flow runner updateon Windows checks a PE build, renamesflow.exetoflow.prev.exeandflow.new.exeinto its place withMoveFileExW(retrying while a virus scanner holds a new file; a running program cannot rename its own file, so a copy of it,flow __rename, does), tells the running runner through anupdate.requestfile in its home (no SIGUSR1 there), and the runner leaves and exits 75 so a Task Scheduler task set to restart on failure starts the new build, which proves itself by reaching the hub. Tried in the Windows VM: install, leave, exit 75, restart, proven. A rollback runs the restored build and ends with it. Spec §11.5.3. flowbuilds for Windows (#83, step 2 begun):cargo build --target x86_64-pc-windows-gnugives aflow.exethat offers the WASM tier: the syscall tier, the guest helpers and the egress proxy are Linux's only (a Windows build says the syscall tier "runs only on Linux"), cache pruning by reads stays on Linux, and file modes, links and sizes go through a smallportablemodule (modes are not kept on Windows). Its claims report free disk (GetDiskFreeSpaceExW); load and kernel are not measured there yet, and a Windows runner does not update itself.distbuildsflow-windows-x86_64.exetoo. Nothing changes on Linux or Android. Tried in the Windows VM on Earth:flow.exe tiersoffers wasm, a localflow runof a 120-step WASM job passes, and registered as runnerwindows-1(labelwindows) it ran andymccall/windows-runner-demo's job from the hub (run #1). Helpers start from the running binary's path on Windows (/proc/self/exeon Linux and Android).- The syscall tier's loopback reaches TCP (#73): with
network "loopback", a job may open IPv4 and IPv6 sockets, which in its own network namespace reach only itslo, so a server it starts on 127.0.0.1 answers it (a conformance suite over an emulator's HTTP control port, which skipped every test on narvi-1). Withnetwork "isolated"sockets other thanAF_UNIXare still refused. - Runner builds in releases (oxygenhub#322): a
distjob (scripts/dist.sh, on a change toCargo.tomlor the script) buildsflowstripped for Linux x86_64, aarch64 and riscv64 (glibc 2.28 and later) asflow-linux-ARCH, named by the architecture a runner reports, withSHA256SUMS: the files a release carries for runners that update themselves. - Runners ask the hub for their update (oxygenhub#322): once the hub has answered a claim, and every 15 minutes, a runner asks
GET /api/v1/flow/runner/update; when the hub offers a build, it downloads it (kept in the home'supdates/), refuses it unless its size and BLAKE3 are those the hub attests, and installs it asflow runner updatedoes, leaving within 600 s, or for a dry run checks it and says what would happen. What it did goes back to the hub once per outcome, and a proof or a rollback is told at the next start.[runner.update] allow = falserefuses and says so; a hub without runner updates (404) is simply asked again later. - The runner update protocol's types (oxygenhub#322):
UpdateAnswer,UpdatePolicy,UpdateOfferandUpdateReportin flow-core, forGET/POST /api/v1/flow/runner/update: the policy that applies to a runner once every level is applied, and the release asset it should run. flow runner update --from FILE(#85): the runner's side of self-updating runners (docs/jetstream.md §8.2). It checks a new build (this machine's target,--version, a tier it can run here), keeps the installed one asflow.prev, swaps the new one in with a single rename, and tells the running runner (SIGUSR1, its pid inrunner.pid) to leave within--noticeseconds (600 by default): jobs that fit finish, the rest are handed back with their checkpoints, and the unit starts the new build. The new build proves itself by getting an answer from the hub; four starts or ten minutes without one putflow.prevback.--dry-runchanges nothing;[runner.update] allow = falseinflow.tomlrefuses every update;updates.login the home records each install, proof and rollback. Spec §11.5.3.- A local
flow runof a WASM or syscall job copies the workspace whole: it copied into a directory it never made, so a file at the top of the workspace (a README) failed withNo such file or directory. - Checkpoints: a handed-back job resumes (#77):
checkpoint "/src/…"on a job names directories that hold its progress; a runner handing the job back packs and keeps them (POST …/checkpoint), and the next attempt's runner restores them before running the same exec, so it carries on rather than starting again. The receipt lists the checkpoint among its inputs. WASM and syscall tiers for now. Spec §11.5.2. - A leaving runner finishes what fits its notice (#76): a claim carries
expected_ms(the median of the job's recent passing runs, from OxygenHub 0.9.4);runner::leave_within(reason, notice)finishes and reports a job whose expected rest fits the notice and hands back the others, and any still running when it is up.runner::leaveis the same with no notice. Spec §11.5.2. - OxygenFlow's own CI writes a test report and coverage (#82): its test job runs under cargo-nextest and cargo-llvm-cov (both in the Rust image, #81), keeping a JUnit report (whether or not the tests pass) and LCOV coverage for the hub's Tests and Coverage tabs;
.config/nextest.tomlhas theciprofile. - A runner that leaves hands its jobs back (#76):
runner::leave(reason)(the runner as a library, as the Android app runs it) stops claiming, stops the jobs it holds, sends what they logged and hands them back withPOST /api/v1/flow/jobs/{id}/release; the hub (OxygenHub #312) queues them again ahead of new work for another runner, and hands back the jobs of a runner silent for 3 minutes the same way. Spec §11.5.2. - A runner reads its load and kernel without /proc:
sysinfoanduname, the same numbers; an Android app may not read/proc/loadavgor/proc/sys/kernel/osrelease, so the phone runner was refused, and logged, at every claim (oxygenhub-android#8). - Built on oxygen's main with #140 and #141: the runner's store puts objects in place where hard links are refused (Android, so the phone runner can clone), and the
deps/oxygenpin conflict two replacements in one change left on main is settled withoxy resolve deps/oxygen. - The WASM tier (#75):
runtime "wasm"runs WASI preview 1 modules with wasmtime 49 on any runner (flow tiersofferswasmeverywhere): no rootfs, no network, the job's files at/src, its mounts at their targets, the memory limit enforced by wasmtime.execnames a module or a glob of them (target/wasm32-wasip1/debug/deps/*.wasmruns each test binary in turn); the receipt names wasmtime's version and every module's hash. The tier phones and browsers will run (docs/handover.md). Secrets and identities are not offered in it yet. - The runner as a library, for the phone (oxygenhub-android#8):
flowis now a library and the command built on it, and the whole runner builds for Android (aarch64-linux-android);runner::serve_embeddedandrunner::stoprun it inside an app and drain it. There the WASM tier runs modules in a thread rather than a child process (also withFLOW_WASM_IN_PROCESS), streaming their output from pipes, stopping them at the time limit or when the run is cancelled through wasmtime's epoch, and reporting the most memory they asked for. Two portability fixes on the way (the seccomp filter's BPF opcodes, for arm64 and x86_64, and an ioctl request type). - cargo-nextest in the Rust image (#81): for JUnit test reports beside
cargo llvm-cov's coverage.
0.3.1 (2026-10-25)
- Runners read repositories with oxy 0.6.0: a tag withdrawn on the hub (oxygenhub#301) is dropped when a runner fetches, a keyless agent's changes verify in a repository that pins its owner's newest key (oxygen#135) or has never seen it (oxygen#134), and shallow clones of big repositories work (oxygen#137). The runner itself is unchanged.
0.3.0 (2026-10-21)
- A runner's own metrics (#35):
metrics = "127.0.0.1:9464"under[runner]serves Prometheus text at/metrics: claims, declines, finished jobs by result, the seconds they ran, the jobs running now, version and uptime, optionally behindmetrics_token. Off unless set. - Built with oxy 0.5.1. A runner reads repositories whose release tags a keyless agent made (oxygen#129), and declares
certified-tagsto the hub (oxygen#131). - Build caches are pruned before they are kept (#72, spec §5.4). A kept cache no longer grows until the job's disk is full. When a passing job's cache holds more than
cache_prune_percentof its mount's size (runner config, default 60), the files the job did not read, write or share a cargo unit hash with are removed, oldest first, until it is under; one still over is not kept, and the next run starts empty. A microVM's guest init prunes its disk before it powers off; the runner prunes the syscall tier's directories. The job's log says how much was kept and pruned, or why the cache was not kept.
0.2.0 (2026-10-20)
-
Approval gates and environments (#57, #59, oxygenhub#235, spec §5.3.2, §5.3.3).
approval { by "maintain"; others-only; expires "24h" }on a job makes the hub hold it until a person with that role (write, maintain by default, or admin) approves it, and withothers-onlynot the person who pushed.approval { sealed #true }asks for an approval signed with the approver's own key (oxy seal --flow), the default for a job that names anenvironment "prod"; the hub names the environment in the job's identity tokens only once it is approved. Both are part of the job's hash. -
Artifacts between jobs (#58, spec §5.3.1).
input { artifact "x" from "job"; path "/src/x" }takes an artifact from a job named independs-on, up to 8 per job. The runner fetches each from the hub, refuses the job when the blob is not the one the producer's receipt names, writes it at its path and lists it in the receipt's inputs. -
Allow-listed egress (#55, spec §4.3.1).
network "egress" { allow "host" "*.example.com" }lets a microVM job reach those names on port 443 and nothing else. The VM still has no network device: a proxy on the runner takes onlyCONNECT host:443for an allowed name, refuses non-public addresses and logs every decision in the job's log; the job getsHTTPS_PROXY. Off unless a runner setsegress = trueunder[runner]; such jobs go only to runners that do. -
Receipts signed with a key that dies with the job (oxygenhub#241). With
keyless = trueunder[runner], the runner makes a fresh key just before signing a job's receipt, has the hub certify it with the job's token, signs, attaches the certificate and drops the key. If the hub cannot certify, the runner's own key signs and the job's log says why. -
A pipeline may lower its jobs' priority (oxygenhub#244).
priority "low"(or"normal") on a job, or at the top of the file for every job, puts it behind other work in the hub's queue.highandurgentare refused with the reason: they are the hub's to give. -
Triggers (#24).
on { push; tag; manual; schedule }for the pipeline and each job:pushandtagtake globs,manualis Run pipeline, andscheduletakes five-field cron lines. A job's ownonwins over the pipeline's; without one a job runs on every push and by hand, as before. -
Runner labels (oxygenhub#41). A sandbox or a job may say
runs-on "LABEL"; a job goes only to a runner that has every label it and its sandbox name (flow runner register --label). -
Images by hash (#39, #44).
flow rootfs push HASHsends an image to a hub, only the chunks it lacks, andflow rootfs pull HASHfetches one. A runner whose job's image is missing fetches it before starting, saying so in the job's log, and keeps the chunks, so a rebuilt image costs only its changed chunks. Both commands use the loginoh auth loginsaved whenOXY_TOKENis not set. -
Runners say what they have and what jobs used (#40, #41). Each claim carries the runner's free disk, load, kernel and a speed class from a short benchmark, taken at start and every 15 minutes while idle (
flow runner benchprints them). A job's finish carries its peak disk and memory, for the hub to learn what its next run needs. -
Reports (oxygenhub#127).
report "NAME" { path "…"; when "…" }in a job's output block uploads a directory holdingreport.toml, file by file with a manifest, within 2,000 files and 512 MiB; the receipt names the manifest. -
Artifacts from a failing job (#37). An artifact with
when "always"is uploaded when the job fails or times out too, so a test report survives the failure it describes. It never goes in the receipt of a job that did not pass. -
A cancelled run stops its jobs (#32). A job whose run is cancelled is stopped: its microVM ends, or never boots if the job was still being prepared, and nothing more is sent.
-
The OpenTofu image (#56).
images/tofu: OpenTofu 1.13.1 with a provider mirror (hashicorp/google, cloudflare/cloudflare), TFLint with its Google ruleset, and curl, jq, xz, sfdisk and debugfs, for deploy jobs. Providers come only from the mirror, sotofu initand linting need no network. -
Coverage in the Rust build image (oxygenhub#113).
cargo-llvm-covand the toolchain'sllvm-toolsare on the guest'sPATH, so a job can write an LCOV report for the hub's Coverage tab. -
The syscall tier keeps its build caches too (#70, spec §5.4). A cached
ephemeral://diskmount in aruntime "syscall"job now starts from the directory a passing job of the same slot kept, and goes back into the runner's pool when the job passes, with the same order, bookmark rules,cache_gbeviction and claim reporting as the microVM tier's disks, so jobs go where their cache is on boards without KVM as well. Nothing kept is writable by others or set-id, and the pool is the runner's alone. -
The build cache pays off (#51, #52, spec §5.4). A cached disk keeps the content hashes and times of the sources it was built from, and the next job gives every unchanged file its old time back, so cargo rebuilds only the crates that changed instead of every path crate. Claims list the caches a runner holds, and the hub sends a job to the runner holding its cache, as it does for images.
-
Restarts cost no job (#49, #50, spec §11.5.1). A job's uploads and declines wait out a hub that is away for up to three minutes (a deploy restarting it), through refused connections as well as server errors, and log lines it could not send go with the next batch instead of being dropped. A runner told to stop drains: it claims nothing more, finishes and reports the jobs it holds, then exits; a second signal stops it at once. With
KillMode=mixedand aTimeoutStopSeclonger than the longest job in its unit, deploying a runner no longer has to wait for it to be idle. -
A build cache (#28, spec §5.4). A disk mount may name a
cacheand thekeyfiles it depends on, such asCargo.lock. The runner keeps passing jobs' disks and starts the next job of the same repository, job and bookmark from one, so builds carry their compiled dependencies forward; another bookmark copies it rather than taking it. Failing jobs' disks are never kept, the receipt names what a disk started from, and the pool stays undercache_gb(default 64 GiB). -
Runners share images (#47, docs/jetstream.md §8.1). A runner with a
[runner.swarm]section serves the image chunks it holds to other runners in its site, and fetches an image it lacks from them and the hub together, rarest chunks first. The hub is asked only for chunks no peer has or is fetching, so runners starting together split its work. Peers talk over TLS: each runner keeps its own certificate, the hub hands its fingerprint on with the peer, and a fetcher trusts no other. Peers admit each other with a ticket the hub signs, and every chunk is checked against its hash. Uploads are capped (upload_mib_s, default 50) at 16 connections. Without the section a runner fetches from the hub alone, as before. -
A runner declines a job it cannot run (#36, spec §11.5). Before fetching anything it checks the tier, the architecture, that the image is there and verifies, and that the sandbox's memory is free. If any check fails it hands the job back to the hub with the reason instead of running and failing it.
-
The syscall tier (#36, docs/m0.md §3).
runtime "syscall"jobs run without KVM, so boards such as the OrangePi RV2 can take them. The sandbox uses user, mount, PID, network, IPC and UTS namespaces on the image's unpacked tree, read-only. Landlock limits writes to/srcand/tmp, and a seccomp filter refuses network sockets, tracing, mounts, BPF, keys, modules and io_uring. A delegated cgroup v2 group enforces the CPU and memory limits, and a job killed for memory is told so. A runner offers the tier only where all of this works.flow tiersshows which tiers a machine can run and why the others cannot, andflow runruns syscall jobs too. -
A runner offers only the tiers it can run (#36). It checks that
/dev/kvmopens before offering the MicroVM tier, says which tiers it serves when it starts, and refuses to start with none, naming why, rather than claiming jobs it would fail. -
A runner tries a fetch again after a network hiccup (oxygenhub#111). Fetching or cloning the job's repository or a subrepo is retried on DNS, connection, timeout and 502/503/504 errors, waiting 2, 4, 8, 16 and 30 seconds; any other error still ends the job at once.
-
Architecture awareness (#36, spec §11.2).
flow rootfs importrecords an image's architecture from its shell's ELF header and refuses an image for another architecture; a claim reports each image's architecture, and a sandbox may sayarch "riscv64". -
A job's identity (oxygenhub#103).
identity "AUDIENCE" { mount-path "/run/secrets/…" }asks the hub for an OIDC token for that audience, mounted as a file like a secret. -
Secrets as files (oxygenhub#107). A job's
secret "name" { mount-path "/run/secrets/…" }names a secret the hub holds; it comes with the claim andflow-initwrites it at that path, 0400, on a tmpfs, before the job starts. Never in the environment, the receipt or an artifact (spec §9).flow run --secret NAME=VALUElocally. -
Variables from the hub (oxygenhub#102). A claim carries
env, the variables the hub holds for the repository; they join the job's environment underFLOW_*and the pipeline's ownenv.flow run --var NAME=VALUEdoes the same locally. -
A job says how long it may run (#23).
timeout "90m"on a job, or on a sandbox for its jobs (hours, minutes, seconds; a minute to a day). The hub caps it at the site's maximum. -
A busy runner still says it is there (oxygenhub#243). A runner whose slots are full, or that is draining, claims nothing, so the hub showed it offline through a quiet job. It now sends a heartbeat every 30 s while it cannot claim.
-
One slow fetch no longer holds every job on a runner (#63). Workspaces were prepared under one runner-wide lock, so a job fetching a large repository from a bucket held all the others (one waited 819 s, and another's token expired before its fetch began). Fetches and exports now take only their repository's lock, and the job's log says how long it waited and how long fetching took.
-
An image with no recorded architecture is read, not trusted (#53). Such an image (imported before architectures were recorded, or fetched from a hub that did not know it) matched every runner, so a riscv64 runner could be handed an x86_64 image and fail. The runner now reads the architecture from the image's shell once, records it, reports it and declines a job for another architecture.
-
flow checksays when a pipeline needs a newer flow (#43). A pipeline that names a node, output or event this flow does not know was called "not valid".flow checkandflow runnow say it uses something this version of flow does not know, and that a pipeline written for a newer OxygenFlow needs a newer flow. -
Pushing a large image works against any hub (oxygenhub#147).
flow rootfs pushasked about 50,000 chunks at once, over a hub's 2 MB body limit, so an 11 GB image failed with 413. It now asks 20,000 at a time. -
A pinned file is re-read from the disk before its hash is called wrong (#22).
-
A runner says which jobs it holds (oxygenhub#71). Each claim lists the jobs the runner is running or still has a result for, so the hub ends at once any job it lost by restarting, instead of an hour later at the time limit.
-
A runner says which images it holds (oxygenhub#73). Each claim lists the rootfs images in the runner's
cas/(read each time, so an image imported while it runs counts), and the hub offers only jobs whose image is among them. plex-1 had failed metalgear's jobs for want of one. -
A job's result is kept until the hub has it (oxygenhub#71). The runner offered a finished job's result three times over seven seconds and then dropped it, so a hub that was briefly down (Earth's reset) left the job running forever. The result is now kept on disk (
pending/in the Flow home) and offered again, every 5 s doubling to a minute, for up to two hours, through any server error or lost connection; a runner that restarts sends what it kept. A refusal (the hub ended the job already) is final. -
The Rust build image vendors mimalloc (#20). Oxygen's new allocator was missing from the image, so oxygen's runs #150 to #153 failed. The image was rebuilt with
images/rust/build.sh, imported on Earth and pinned in the oxygen, oxygenhub and oxygenflow pipelines. Rebuild it whenever aCargo.lockof those projects changes. -
A hint for shell escapes in a pipeline. KDL 2.0 strings have no
\033,\eor\xescapes; a pipeline that uses one (for ANSI colours, say) is still invalid, butflow checkand the hub now name the line and say to write ESC as\u{1b}.