OXYGENHUB

Changelog per change: a check in the gate, and Unreleased caught up da5118d25db2

From now on a commit that changes code adds a line under ## Unreleased
(scripts/changelog-check.sh, run in the gate; NO_CHANGELOG=1 for changes
users won't notice), and versions change only at release. Unreleased now
lists everything since the last release.
Author
Andy McCall @andymccall <[email protected]>
via
agent agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key f4b13c9cf762
When
· 2026-10-19 20:16 UTC
Signature
◈ sealed by agent:claude@andymccall
Hash
da5118d25db2bdd06fe63917e2b1736b4d6c82c8862deab63bcb0782091d6a9a
Depends on
02b127d2c721 2594278f49b2 a083cc802892 c3db750ed96e
Depended on by
5544c8998aa7 72772aa01d6b da72c3b192c3
In bookmarks
main
In tags
v0.2.0 v0.3.0 v0.3.1 v0.4.0 v0.5.0 v0.5.1 v0.5.2 v0.5.3
Edits
4 atoms, 2 tree operations, 3 files

Browse the tree at this change

added scripts/changelog-check.sh +27 −0
1 +
#!/bin/sh
2 +
# The changelog check (2026-10-19): a commit that changes what users get
3 +
# adds a line under "## Unreleased" in CHANGELOG.md, so a release's notes
4 +
# are written as the work is done and versions say what changed.
5 +
#
6 +
#   scripts/changelog-check.sh [PATH...]
7 +
#
8 +
# PATHs are what the commit will take (as given to `oxy commit`); without
9 +
# any, the working copy's changes. Code is anything under src/, crates/,
10 +
# templates/, assets/, packaging/, images/ or scripts/, except tests.
11 +
# Docs, pipelines, pins (deps/) and lock files need no line. For a code
12 +
# change users won't notice (a refactor, a test helper), set NO_CHANGELOG=1.
13 +
set -eu
14 +
[ "${NO_CHANGELOG:-}" = 1 ] && exit 0
15 +
cd "$(dirname "$0")/.."
16 +
if [ "$#" -gt 0 ]; then
17 +
    paths=$(printf '%s\n' "$@")
18 +
else
19 +
    paths=$(oxy --plain status --json | python3 -c 'import json,sys; print("\n".join(p["path"] for p in json.load(sys.stdin)["paths"]))')
20 +
fi
21 +
code=$(printf '%s\n' "$paths" | grep -E '^(src|crates|templates|assets|packaging|images|scripts)/' | grep -vE '(^|/)tests?/|_test\.rs$' || true)
22 +
[ -z "$code" ] && exit 0
23 +
printf '%s\n' "$paths" | grep -qx 'CHANGELOG.md' && exit 0
24 +
echo "changelog: these changes touch code but CHANGELOG.md has no new line under ## Unreleased:" >&2
25 +
printf '%s\n' "$code" | sed 's/^/  /' >&2
26 +
echo "add one (Added / Changed / Fixed / Security, with the issue number), or set NO_CHANGELOG=1 if users won't notice" >&2
27 +
exit 1
modified CHANGELOG.md +38 −0
…
3 unchanged lines
4 4
5 5
## Unreleased
6 6
7 +
- **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 with `others-only` not 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 an `environment "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.
8 +
9 +
- **Artifacts between jobs (#58, spec §5.3.1).** `input { artifact "x" from "job"; path "/src/x" }` takes an artifact from a job named in `depends-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.
10 +
11 +
- **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 only `CONNECT host:443` for an allowed name, refuses non-public addresses and logs every decision in the job's log; the job gets `HTTPS_PROXY`. Off unless a runner sets `egress = true` under `[runner]`; such jobs go only to runners that do.
12 +
13 +
- **Receipts signed with a key that dies with the job (oxygenhub#241).** With `keyless = true` under `[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.
14 +
15 +
- **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. `high` and `urgent` are refused with the reason: they are the hub's to give.
16 +
17 +
- **Triggers (#24).** `on { push; tag; manual; schedule }` for the pipeline and each job: `push` and `tag` take globs, `manual` is Run pipeline, and `schedule` takes five-field cron lines. A job's own `on` wins over the pipeline's; without one a job runs on every push and by hand, as before.
18 +
19 +
- **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`).
20 +
21 +
- **Images by hash (#39, #44).** `flow rootfs push HASH` sends an image to a hub, only the chunks it lacks, and `flow rootfs pull HASH` fetches 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 login `oh auth login` saved when `OXY_TOKEN` is not set.
22 +
23 +
- **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 bench` prints them). A job's finish carries its peak disk and memory, for the hub to learn what its next run needs.
24 +
25 +
- **Reports (oxygenhub#127).** `report "NAME" { path "…"; when "…" }` in a job's output block uploads a directory holding `report.toml`, file by file with a manifest, within 2,000 files and 512 MiB; the receipt names the manifest.
26 +
27 +
- **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.
28 +
29 +
- **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.
30 +
31 +
- **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, so `tofu init` and linting need no network.
32 +
33 +
- **Coverage in the Rust build image (oxygenhub#113).** `cargo-llvm-cov` and the toolchain's `llvm-tools` are on the guest's `PATH`, so a job can write an LCOV report for the hub's Coverage tab.
34 +
7 35
- **The syscall tier keeps its build caches too (#70, spec §5.4).** A cached `ephemeral://disk` mount in a `runtime "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_gb` eviction 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.
8 36
9 37
- **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.
…
21 unchanged lines
31 59
- **Variables from the hub (oxygenhub#102).** A claim carries `env`, the variables the hub holds for the repository; they join the job's environment under `FLOW_*` and the pipeline's own `env`. `flow run --var NAME=VALUE` does the same locally.
32 60
33 61
- **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.
62 +
63 +
- **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.
64 +
65 +
- **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.
66 +
67 +
- **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.
68 +
69 +
- **`flow check` says 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 check` and `flow run` now say it uses something this version of flow does not know, and that a pipeline written for a newer OxygenFlow needs a newer flow.
70 +
71 +
- **Pushing a large image works against any hub (oxygenhub#147).** `flow rootfs push` asked 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.
34 72
35 73
- **A pinned file is re-read from the disk before its hash is called wrong (#22).**
36 74
…
7 unchanged lines
modified README.md +3 −0
…
108 unchanged lines
109 109
110 110
## Tests
111 111
112 +
**Changelog:** a commit that changes code adds a line under `## Unreleased` in CHANGELOG.md (Added, Changed, Fixed or Security, with the issue number); versions change only at release. `scripts/changelog-check.sh [PATH...]` checks it for the paths a commit takes (the working copy's changes without any) and runs in the gate; `NO_CHANGELOG=1` for code changes users won't notice.
113 +
114 +
112 115
`cargo test --workspace` runs every test that needs no VM. The microVM
113 116
conformance test also runs when `FLOW_TEST_HOME` names a Flow home and
114 117
`FLOW_TEST_ROOTFS` is the hash of an Alpine rootfs in it. The test checks
…
6 unchanged lines