Skip to content

Every app the images ship is built or checked for Linux, macOS and Windows, or its manifest says why it cannot - #674

Merged
Japabu merged 10 commits into
mainfrom
wt/toyos-hostapps
Oct 1, 2026
Merged

Japabu merged 10 commits into
mainfrom
wt/toyos-hostapps

Conversation

@Japabu

@Japabu Japabu commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

The owner's rule: an app builds and runs on Linux under Wayland, macOS and Windows from the same source as on ToyOS, and a host build that fails is fixed in the app or its dependencies. A program is ToyOS-only by nature when its job exists only on ToyOS: it owns ToyOS devices or kernel objects, or it manages ToyOS itself. calc broke the rule unseen. This PR writes the rule where it is enforced, and gates it.

What changed, per decision

What the gate judges is what the images ship. build::shipped gains programs: its crates without the kernel and the loader, from the same config_crates walk that the build and the licence gate read. userlandhost::programs reads each program's [package.metadata.toyos.host]:

  • nothing: the program is an app;
  • exempt.owns or exempt.manages, with what and why: the program is ToyOS-only;
  • fails = [<hosts>] with issue: an app that does not build on those hosts yet.

Any other key or shape is refused by name. This takes in toyos-ld, which is outside the userland workspace. test-runner and metalprobe ship in no mode's image, so nothing reads a declaration of theirs, and they carry none.

An exemption names one of the owner's two cases.

  • exempt.owns: the program owns ToyOS devices or kernel objects. That is blockd, compositor, console, fsd, init, logd, netd and soundd. Each names the device, SysCap or kernel object it claims. A port it serves is no part of the exemption: filepicker serves one, and it is an app.
  • exempt.manages: the program manages ToyOS itself. That is inspect, swap and update.

userland/CLAUDE.md and the reviewer's Hosts bullet say the same two cases.

filepicker is an app. Its exemption went, and the gate judges it on every host, as it judges the other apps. Making it run on a host is not small:

  • toyos-window reaches no display off ToyOS, as with editor, files and paint;
  • filepicker-api reaches the picker only through the filepicker port.

issues/build/apps-that-build-or-run-on-toyos-alone.md records both, with an exit.

A host's apps are built where the gate runs on that host's triple, and checked elsewhere. apps_for runs cargo build --target <triple> when the triple is the host's own, and cargo check --target <triple> otherwise. Either way it uses the features the image builds the app with.

  • The PR runner, ubuntu-24.04, builds and links Linux's apps, and checks macOS's and Windows's.
  • The nightly's macos-latest runner builds macOS's apps.

So two runners can give one host two different verdicts. That deadlocks nothing, because a declared host is attempted on no runner: a failure that only one runner sees is answered by fails.

A host an app's fails names is not attempted. So the PR gate never runs pkg-config, which cpal's alsa-sys probes on Linux. It never fetches doomgeneric either: doom's build.rs fetches it before it reads CC_<target>. Nothing is declared for either.

The cost is that a fails entry that outlives its fix keeps that host unjudged. A ratchet would attempt the declared hosts and go red when one builds. While two runners judge one host, that ratchet deadlocks: a fix that passes on one runner and fails on the other is red whether its entry stays or goes. issues/build/a-hosts-apps-are-judged-on-other-hosts-runners.md records this with the other gaps of judging a host from another host's runner. Its exit attempts each declared host on that host's own runner.

The two per-host decisions are pure functions.

  • attempted separates the apps judged for a host from those whose fails names it.
  • verb picks build or check.

an_app_is_judged_for_every_host_its_fails_does_not_name covers both, with no cargo run.

A fails entry's issue is work still owed. It must be a path issues/<area>/<slug>.md, its front matter's status must be open or assigned, and the file must name the app in backticks.

socket2. ToyOSOrg/socket2 toyos commit 0b238fb gives use crate::MsgHdrMut the same condition as recvmsg, its only user. That condition is upstream's all(unix, not(target_os = "redox")), with ToyOS named beside it. userland/Cargo.lock moves its one socket2 entry to 0b238fb. No other lockfile or gitlink names that branch.

Also:

  • Five apps build on no host yet: doom, toybox, terminal, shell and proctest. Each declares fails = ["linux", "macos", "windows"] against issues/build/apps-that-build-or-run-on-toyos-alone.md.
  • sshd builds on Windows through the socket2 fork.
  • implementer.md and the reviewer's Fit bullet carry the dependency rule.
  • orchestrator.md gains one line under Judge.
  • userland/README.md is deleted.

Gates

On the macOS dev host (arm64), at 207bdde:

  • cargo run -- --ci host: EXIT=0, "[ci] Host: 59 step(s), all green". The three apps steps read:

    • "11 app(s) pass cargo check --target x86_64-unknown-linux-gnu";
    • "11 app(s) pass cargo build --target aarch64-apple-darwin";
    • "11 app(s) pass cargo check --target x86_64-pc-windows-msvc".

    Each step adds "userland/doom, userland/proctest, userland/shell, userland/terminal, userland/toybox not attempted, as their manifests declare".

  • cargo test --lib -- ci::tests userlandhost every_modes_crates_and_init_ship: EXIT=0, 21 tests.

  • cargo clippy -p toyos-build --all-targets -- -D warnings -W clippy::redundant_clone: EXIT=0.

  • cargo run -- --build-only: EXIT=0, "Boot image: …/target/bootable.img".

  • What the build costs over the check, from an empty target directory on the same host, for the 11 apps and aarch64-apple-darwin: checking took 20 s and 443 MB, and building took 26 s and 1.2 GB, exit 0 each.

  • cargo tree --target x86_64-unknown-linux-gnu -e normal,build -i alsa-sys gives EXIT=101 for each of the 11 apps the Linux runner builds, "did not match any packages". toybox, as the positive control, gives EXIT=0, with alsa-sys under alsa and cpal.

Measured on socket2 alone at 0b238fb: RUSTFLAGS="-D warnings -A unexpected_cfgs" cargo check --all-features gives EXIT=0 for x86_64-pc-windows-msvc, x86_64-unknown-linux-gnu and aarch64-apple-darwin. unexpected_cfgs is allowed because stable rustc knows no toyos target and warns on every arm that names one.

Negative controls

Each mutation below is a checked patch. It was applied, built (cargo test --lib --no-run, EXIT=0), run and reverted at 207bdde, and git status was clean before and after. The patches and the runners are posted as a comment on this PR.

The per-host decisions, cargo test --lib -- --exact ci::tests::an_app_is_judged_for_every_host_its_fails_does_not_name:

  • unmutated: EXIT=0;
  • in attempted, if fails.contains(&os) becomes if !fails.is_empty(): EXIT=101, "left: (["calc"], ["doom"]) right: (["calc", "doom"], [])";
  • in verb, == becomes !=: EXIT=101, "left: ["check", "build", "build"] right: ["build", "check", "check"]".

The gate end to end. A measurement test calls apps_for for each host on the real tree, with this host's triple:

  • unmutated: EXIT=0.
    • 11 apps pass on each host: built for macOS, checked for Linux and Windows.
    • doom, proctest, shell, terminal and toybox are "not attempted, as their manifests declare".
  • console loses its exempt.owns: EXIT=101. It is red on all three hosts with "userland/console fails for and declares neither fails there nor exempt: cargo check … exited exit status: 101". For macOS the message reads cargo build.
  • the fails skip removed (if false && fails.contains(&os)): EXIT=101, red for doom, proctest, shell, terminal and toybox on all three hosts.

The reader, cargo test --lib -- userlandhost every_modes_crates_and_init_ship, each run EXIT=101:

  • programs keeps only programs under userland/: every_program_the_images_ship_declares_what_it_is_to_a_host fails, with toyos-ld on one side only;
  • Shipped::programs takes workspace members alone: every_modes_crates_and_init_ship fails;
  • the issue-path shape check disabled: a_host_declaration_is_read_whole_and_refused_by_name fails on issue = "notes.md";
  • the status check disabled: the same test fails on issues/build/asked.md, status: owner;
  • any exemption case accepted: the same test fails on exempt.serves.

Independent oracle: cargo's own exit for each app on each host's triple, and rustc's own warning for the fork. The gate reads nothing else but the manifests.

Unsure

  • A check is not a build. On a pull request macOS's and Windows's apps are only checked, and Windows's are built nowhere. A link failure, or an error only code generation raises, goes unseen there. This is recorded in issues/build/a-hosts-apps-are-judged-on-other-hosts-runners.md.
  • Build scripts run on the host that checks. An app whose build compiles C or probes pkg-config cannot pass a check of another host's triple. Today only doom and toybox are like that, and both are declared failing on all three hosts. Recorded in the same issue.
  • A declared failure is held to nothing. A fails entry that has gone stale is not caught. Recorded in the same issue, with the reason a ratchet is unsound while two runners judge one host.
  • A cfg that compiles an app's work out of a host checks green there. That was the calc incident's first shape. Only review holds it (.claude/agents/reviewer.md, Hosts), and the module doc says so.
  • issues/build/the-build-system-does-not-compile-on-windows.md has no owner and no exit, and holds an owner's question. It is not this PR's file. Until it changes, the new issue's Windows exit and doom's windows entry have no reachable end.

Size

Net lines, git diff --shortstat origin/main...HEAD: +512 −38 across 28 files.

  • src production: +275 −5.
    • src/userlandhost.rs: +184 −2, of which 31 lines are module doc.
    • src/ci.rs: +81 −1.
    • src/build.rs: +10 −2.
  • tests: +104.
  • manifests: +53.
  • the two issues: +64 −25.
  • rule text: +15 −7, userland/README.md included.
  • lockfile: one line.

🤖 Generated with Claude Code

https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L

Japabu and others added 6 commits October 1, 2026 14:09
…hy it cannot

The owner's rule: an app builds and runs on Linux under Wayland, macOS
and Windows from the same source as on ToyOS, and a host build that
fails is fixed in the app or its dependencies, never by making the app
ToyOS-only. The one exemption is a program that by its nature cannot
run elsewhere: a system server that owns ToyOS devices or kernel
objects, like the compositor. calc broke the rule unseen, because CI
built apps on macOS alone.

The gate. `src/userlandhost.rs`'s `packages` reads every member of the
userland workspace and its own manifest's
`[package.metadata.toyos.host]`: nothing, for an app; `exempt = "<why>"`;
or `fails = [<hosts>]` with the `issue` that records them, which must
name the app. Any other key or shape is refused by name.
`cargo run -- --ci host` gains a step per host OS. Each builds every app
for this host, and `cargo check`s it against the other two hosts'
triples. `judge` reds where a build and its declaration disagree, both
ways, so a declared failure goes when the app builds. It runs one cargo
per app, because features unify across the packages of one invocation.
A Windows runner cannot run this build system
(issues/build/the-build-system-does-not-compile-on-windows.md), so
Windows is checked from the others.

Fourteen members are exempt, each with its reason in its manifest:
blockd, compositor, console, filepicker, fsd, init, inspect, logd,
metalprobe, netd, soundd, swap, test-runner and update. Each claims a
device, holds a SysCap, a slot or init's swap port, serves a
machine-wide port, or is init. An exemption takes a package out of the
build gate only: the host tests of blockd, fsd, logd, netd and soundd
still run. libc is no member of the userland workspace. It is the
sysroot's C library, and its manifest is part of the sysroot's and the
toolchain release's key.

The rule text:
- userland/CLAUDE.md states it.
- reviewer.md gains it as a BLOCKER. Its "No new dependency or fetch"
  becomes the dependency rule the owner approved, the same as
  implementer.md's, because a reviewer still reading the old line would
  block what the implementer is now told to do.
- implementer.md's "No new dependency." becomes that rule.
- orchestrator.md: a "blocked" report is checked against the code and
  the product's principles before any ruling, and no product is changed
  to make a check green.

userland/README.md goes. Its one sentence was this rule without the
exemption, and nothing loaded it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
`cargo check --manifest-path userland/sshd/Cargo.toml --target
x86_64-pc-windows-msvc` stopped in socket2: "cannot find function
`recvmsg` in module `sys`". The ToyOS port of socket2 (a35837c) had
widened `Socket::recvmsg` from upstream's
`all(unix, not(target_os = "redox"))` to every target but Redox and
WASI, which takes in Windows. The fork's `toyos` branch gains 48d15f5,
which names ToyOS beside upstream's condition, as its other arms do.
This moves userland/Cargo.lock's one socket2 entry onto it. No other
lockfile or gitlink names that branch.

Measured on the macOS dev host:
- socket2 alone, `cargo check --all-features`: x86_64-pc-windows-msvc
  exits 101 at 2b67e7b, and 0 at 48d15f5. x86_64-unknown-linux-gnu and
  aarch64-apple-darwin exit 0 at 48d15f5.
- sshd for x86_64-pc-windows-msvc against this lockfile exits 0.
- `cargo run -- --build-only`, with userland's socket2 taken from
  48d15f5 through a file:// git source and the tree restored after,
  exits 0. The log compiles socket2 at 48d15f59 and then sshd for ToyOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Two cases in the fixture test reached the catch-all arm before the check
they were meant for: a misspelt key alone, and an `issue` outside
issues/ that did not exist. Now an unknown key sits beside a valid
`exempt`, and the outside path exists and names the app. Each check,
mutated away as a checked patch, turns
`a_host_declaration_is_read_whole_and_refused_by_name` red with exit
101: the unknown-key filter made to match nothing, and the issues/
prefix test made to refuse only an empty path. The tree was restored
after both.

`Os::this` is `Os::current`, and the judge's two refusals say what is
missing in fewer words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
The Hosts bullet had landed inside Fit, ahead of Fit's architecture
lines, which it would have taken as its own. It now follows Fit.

The owner ruled that on Linux, ToyOS apps support Wayland alone: X11 is
legacy, and no X11 backend feature is added or kept. Where the rule
text names Linux it now says Wayland, userland/CLAUDE.md says never X11,
and an X11 backend is a reviewer's BLOCKER.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
#667 filed what still built or ran on ToyOS alone. This takes that
issue over, renamed, because the tree refutes its slug's claim:
editor, files, paint and filepicker build on all three hosts.
`issues/build/apps-on-toyos-window-terminal-toybox-and-doom-build-for-toyos-alone.md`
is now `issues/build/apps-that-build-or-run-on-toyos-alone.md`. Nothing
cited the old name.

The set is now the one the gate measures. doom, toybox, terminal, shell
and proctest build on none of Linux, macOS and Windows. Each declares
`fails = ["linux", "macos", "windows"]` against the issue, which names
each. Measured on the macOS dev host, natively and by
`cargo check --target` for the other two:
- shell and proctest reach `std::os::toyos`, as terminal and toybox do.
  #667's Linux-only table could not show it for those two.
- filepicker leaves the issue. It is exempt: init starts it at boot to
  serve the machine-wide `filepicker` port.
- editor, files and paint keep their row. They build, and toyos-window
  reaches no display but ToyOS's compositor.

The exit now says what the gate can fail: no manifest names the file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu
Japabu marked this pull request as ready for review October 1, 2026 12:56
@Japabu Japabu changed the title Every userland app builds on Linux, macOS and Windows, or its manifest says why it cannot Every userland app builds on Linux, macOS and Windows on each pull request, or its manifest says why it cannot Oct 1, 2026
@Japabu

Japabu commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Review of 16ba295. CI: host success, run 36865166000 at this head ("[ci] Host: 59 step(s), all green"); the three new userlandhost tests ran ok. No hardware target.

BLOCKER

  1. issues/build/the-build-runs-host-tools-outside-rust-and-qemu.md — pkg-config has no row, and this gate runs it on every Linux host job — the linux step builds doom and toybox, whose alsa-sys build script ran pkg-config --libs --cflags alsa twice in run 36865166000 ("pkg-config exited with status code 1"); main's merge-queue run 36862873592 runs it nowhere. An undeclared host tool.
  2. src/ci.rs:580 with userland/doom/build.rs:22-35 — the required check now fetches doomgeneric from github.com on every fresh checkout — doom's build.rs downloads before it reads CC_<target> (line 46), and run 36865166000's macos step printed "Downloading doomgeneric fc601639…" only to panic on "CC_aarch64_apple_darwin is unset"; run 36862873592 fetched nothing. reviewer.md:46, as this diff writes it: no new fetch.
  3. src/ci.rs:565-566 — one OS has two judges and fails is per OS — Linux is built natively on the PR runner and cargo checked from the nightly's macOS runner, macOS the reverse. alsa-sys's build script refuses every cross build (pkg-config 0.3.32 target_supported, src/lib.rs:583 and 606-626), and doom's build.rs compiles C for the target, which a cross-check cannot do for another OS. The first fix that builds toybox or doom on Linux reds the PR job until linux leaves fails, and then reds the nightly: neither can ever leave issues/build/apps-that-build-or-run-on-toyos-alone.md, whose exit the issue, the module doc and the PR body call reachable.
  4. src/userlandhost.rs:195-216 — the gate's universe is a second list, userland's [workspace] members, beside build::shipped (src/build.rs:1093-1112), which is what the images are built from and what the licence gate reads (src/licence.rs:1186) — a shipped program outside the userland workspace is judged nowhere: toyos-ld today (system.toml:44), and any app split into a package userland's members does not list, the calc incident's shape. Mutation that stays green: snake leaves members for a crate of its own with path in its row, as toyos-ld is. Test it must turn red: every crate build::shipped names, kernel and bootloader aside, is judged by apps_on or declared; red today on toyos-ld. Under that list test-runner and metalprobe, which no mode's image ships, need no exemption.
  5. userland/filepicker/Cargo.toml:14 — wrong by nature — its work is listing a directory with std::fs and drawing a dialog; the filepicker port and toyos-window are its transport, which filepicker-api has to carry on a host anyway for editor and paint to pick a file there. The reason says how init deploys it, which any app can share. It builds on all three today (16ba295's message), so the exemption only takes it out of the gate and its "runs on none" row out of the issue: the exempt this diff's own Hosts bullet (reviewer.md:54-58) makes a BLOCKER.
  6. userland/CLAUDE.md:5, .claude/agents/reviewer.md:57, src/userlandhost.rs:8-9 — the criterion is written two ways — "a system server that owns ToyOS devices or kernel objects" in both instructions, "owns ToyOS devices or kernel objects" in the module doc. inspect, swap, update, metalprobe and test-runner serve nothing, so the instructions refuse five exemptions this diff lands, though each is ToyOS-only by nature. One declaration moves; which one is the owner's ruling.
  7. ToyOSOrg/socket2 48d15f5, src/socket.rs:24 — not upstream-mergeable — use crate::MsgHdrMut; keeps the port's widened #[cfg(not(any(target_os = "redox", target_os = "wasi")))] while its only user, recvmsg (line 635), narrows to any(all(unix, not(target_os = "redox")), target_os = "toyos"): on Windows the import is unused and warns, and the line is not upstream's all(unix, not(target_os = "redox")). Patch: line 24 takes line 635's cfg. Measurement red at 48d15f5 and green after: RUSTFLAGS=-Dwarnings cargo check --all-features --target x86_64-pc-windows-msvc in the fork; the exit-0 check in the PR body cannot see a warning. userland/Cargo.lock then moves to the new head.
  8. src/ci.rs:551-555 — a compromise recorded only in the PR body — Windows apps are type-checked and never built, linked or run anywhere, and macOS apps are built only by the nightly, on main. No file in issues/ holds it with an owner and an exit (a Windows leg running cargo run -- --ci host, behind issues/build/the-build-system-does-not-compile-on-windows.md), while the title says every app builds on all three on each pull request.

NOTE

  • Net +375 (+411 −36): src production +242 (userlandhost.rs +181, about 25 of them module doc; ci.rs +61), tests +63, manifests +62, prompts +7, issue +1. The growth is the gate the owner asked for, accepted; 4 and the next line delete packages's member reader, Os::current and two exemptions.
  • src/ci.rs:565 — Os::current()? reds all three app steps on any other host (FreeBSD; ToyOS once it builds itself), where three cross-checks would serve — Os::named(std::env::consts::OS) == Some(os), and current goes.
  • Exemptions held by nature: blockd, compositor, console (it claims the raw framebuffer and keyboard), fsd, init, inspect (its work is ToyOS's own owners and kernel inventory), logd, metalprobe, netd, soundd, swap (init's half of a ToyOS protocol), test-runner, update. filepicker is not (5); five of these fail the instructions' wording (6).
  • src/userlandhost.rs:1-48 — the gate reads a build's exit, so an app whose work a cfg(target_os = "toyos") compiles out of a host builds green on all three, the incident's first shape — only reviewer.md:54-58 holds it, and neither the module doc nor the PR body's Unsure says so.
  • src/userlandhost.rs:258-265 — issue may be any file under issues/ that names the app in backticks (issues/README.md, issues/../…) — nothing checks it is an open issue.
  • issues/build/apps-that-build-or-run-on-toyos-alone.md:22-26 — editor, files and paint are tied to no manifest, so "no manifest names this file" can hold while they still run on none, and nothing checks "runs".
  • PR Rules and records: the ABI and kernel rulings, the no-panic track, the owner's 2026-09-30 rulings, CPU microcode, the crate track, the LLVM-bytes and TCO-overflow defects #673 edits the same lines — .claude/agents/implementer.md:58-61 (Rules and records: the ABI and kernel rulings, the no-panic track, the owner's 2026-09-30 rulings, CPU microcode, the crate track, the LLVM-bytes and TCO-overflow defects #673 deletes "Never touch toyos-abi/src, … unless the brief is an ABI brief." and keeps "No new dependency.") and .claude/agents/reviewer.md:44-47 (Rules and records: the ABI and kernel rulings, the no-panic track, the owner's 2026-09-30 rulings, CPU microcode, the crate track, the LLVM-bytes and TCO-overflow defects #673 adds "A BLOCKER each: a kernel addition that userland could own; a design made worse to spare the ABI." and keeps "No new dependency or fetch."). Whichever lands second carries Rules and records: the ABI and kernel rulings, the no-panic track, the owner's 2026-09-30 rulings, CPU microcode, the crate track, the LLVM-bytes and TCO-overflow defects #673's deletion and BLOCKER line and this PR's dependency rule; either side taken whole reverts the other.
  • Root CLAUDE.md does not grow; userland/CLAUDE.md:5 grows by a paragraph, offset only by deleting userland/README.md — the rule's third copy, beside src/userlandhost.rs:4-27 and reviewer.md:54-58, against "a CLAUDE.md never grows": the brief's placement, the orchestrator's to uphold. Each edit is in its file's style.
  • issues/build/the-build-system-does-not-compile-on-windows.md — recorded with its seven errors as evidence, but no owner and no exit — its judge is a compile the same file says proves nothing, and it ends on an open design question; root CLAUDE.md's "on any host OS" is false for Windows while it stands. Pre-existing; this PR rests Windows coverage on it.
  • The fork pin is otherwise right: userland/Cargo.lock is the only lockfile or gitlink naming ToyOSOrg/socket2?branch=toyos (rust/Cargo.lock takes socket2 0.6.3 from crates.io), and it names the branch head.

REMOVE

  • PR title — "on each pull request" — macOS and Windows are cargo checked there, not built.
  • PR body — "The issue's exit is one the gate can fail: no manifest names the file." — false for doom and toybox (3).
  • PR body, Unsure — "Five exemptions own a ToyOS object without serving a port: console, inspect, metalprobe, swap and update." — test-runner serves nothing either, and console provides surface.
  • src/userlandhost.rs:27 — ": so a declared failure goes when the app builds" — false where a cross-check is the judge (3).

SEND BACK

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Japabu and others added 2 commits October 1, 2026 15:53
ToyOSOrg/socket2 `toyos` 48d15f5 narrowed `Socket::recvmsg` to
upstream's condition with ToyOS beside it, and left the
`use crate::MsgHdrMut` only `recvmsg` reads under the port's wider one,
so on Windows the import is unused and warns. 0b238fb gives the import
`recvmsg`'s condition. In the fork,
`RUSTFLAGS="-D warnings -A unexpected_cfgs" cargo check --all-features
--target x86_64-pc-windows-msvc` exits 101 at 48d15f5 on that warning
alone, and 0 at 0b238fb, as it does for x86_64-unknown-linux-gnu and
aarch64-apple-darwin at both. `unexpected_cfgs` is allowed because
stable rustc knows no `toyos` target and warns on every arm naming it.

`userland/Cargo.lock` is the one lockfile or gitlink naming the
branch, and its socket2 entry moves to 0b238fb.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
…y everywhere, and an exemption names its case

The review of 16ba295 sent the gate back on its universe, its judges and
two host needs it did not declare. Each is answered at its root.

The universe is `build::shipped`. `Shipped` gains `programs`, its
`crates` but the kernel and the loader, from the same `config_crates`
walk the build and the licence gate read; `userlandhost::programs` reads
each one's manifest. A program outside the userland workspace is judged
now: toyos-ld, which checks for all three triples. test-runner and
metalprobe ship in no mode's image, so nothing reads a declaration of
theirs and both go.

One judgement per host. Every host's verdict is `cargo check --target`
its triple, on whichever host runs `--ci host`. Before, the PR runner
built Linux natively and the nightly's macOS runner checked it, and macOS
the reverse, so one `fails` entry was right on one runner and wrong on
the other. `Os::current` goes with the native arm. Linux is no longer
linked on the PR runner: linking there would make Linux's verdict
differ again from the macOS runner's check of it.
`issues/build/no-app-is-built-for-a-host-each-is-only-checked.md`
records what a check cannot see, on every host, with its exit.

A host an app's `fails` names is not attempted. The gate compiled doom
and toybox only to see them fail: on Linux cpal's `alsa-sys` ran
`pkg-config`, and doom's build script fetched doomgeneric before it read
`CC_<target>`. Neither runs now. The ratchet that made a declared failure
that builds red goes with it, so a stale `fails` stays until the one who
fixes the app removes it.

An exemption names its case, the owner's two: `exempt.owns`, the ToyOS
devices or kernel objects a program owns (blockd, compositor, console,
fsd, init, logd, netd, soundd), or `exempt.manages`, the part of ToyOS
it manages (inspect, swap, update), each with what and why. Any other
shape is refused by name. filepicker is no longer exempt: it is a file
dialog over `std::fs`, and its port is only how it is reached. It checks
for all three triples, and the issue records that it runs on none, with
editor, files and paint.

A `fails` entry's `issue` must be `issues/<area>/<slug>.md` whose
front matter says `open` or `assigned`: `issues/README.md` and
`issues/../…` are refused.

`userland/CLAUDE.md` and the reviewer's Hosts bullet state the two
cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu

Japabu commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2's negative controls, as the checked patches the PR body names. Each applies to 33f357f (git apply --check), and each run was built, run and reverted to a clean tree by the runner scripts below.

The measurement harness, applied under the three gate cases (unmutated, m1-console-unexempt, m2-no-skip):

diff --git a/src/ci.rs b/src/ci.rs
index eb5d4b943..724806ce1 100644
--- a/src/ci.rs
+++ b/src/ci.rs
@@ -859,6 +859,17 @@ fn at_tip(ls_remote: &str, head: &str) -> Result<(), String> {
 mod tests {
     use super::*;
 
+    #[test]
+    fn measure_apps() {
+        let root = Path::new(env!("CARGO_MANIFEST_DIR"));
+        let programs = crate::userlandhost::programs(root).expect("programs");
+        let verdicts: Vec<_> = Os::ALL.into_iter().map(|os| (os, apps_for(root, &programs, os))).collect();
+        for (os, verdict) in &verdicts {
+            eprintln!("MEASURE {os:?}: {verdict:?}");
+        }
+        assert!(verdicts.iter().all(|(_, v)| v.is_ok()), "a host is red");
+    }
+
     /// A deterministic control on `host`'s `std::env::set_var("TMPDIR", ...)`:
     /// delete that line and every child writes to the real `$TMPDIR` instead of
     /// this job's private one, which never gains the lock a real step always

m1-console-unexempt:

diff --git a/userland/console/Cargo.toml b/userland/console/Cargo.toml
index 4e35113d4..04acb0730 100644
--- a/userland/console/Cargo.toml
+++ b/userland/console/Cargo.toml
@@ -11,6 +11,3 @@ toyos-abi = { path = "../../toyos-abi" }
 toyos-font = { path = "../toyos-font" }
 toyos-window = { path = "../toyos-window" }
 toyos-logstream = { path = "../../toyos-logstream" }
-
-[package.metadata.toyos.host]
-exempt.owns = "ToyOS's framebuffer, keyboard and mouse, claimed from the kernel, on which it runs the shell"

m2-no-skip:

diff --git a/src/ci.rs b/src/ci.rs
index eb5d4b943..71c72e149 100644
--- a/src/ci.rs
+++ b/src/ci.rs
@@ -566,7 +566,7 @@ fn apps_for(root: &Path, programs: &[Program], os: Os) -> Result<String, String>
     let (mut checked, mut declared, mut red) = (0, Vec::new(), Vec::new());
     for program in programs {
         let Host::App(fails) = &program.host else { continue };
-        if fails.contains(&os) {
+        if false && fails.contains(&os) {
             declared.push(program.dir.as_str());
             continue;
         }

m3-userland-only:

diff --git a/src/userlandhost.rs b/src/userlandhost.rs
index 7cb912ba8..c06fb8ee1 100644
--- a/src/userlandhost.rs
+++ b/src/userlandhost.rs
@@ -195,7 +195,7 @@ pub struct Program {
 /// declares about the hosts.
 pub fn programs(root: &Path) -> Result<Vec<Program>, String> {
     let mut found = Vec::new();
-    for (dir, features) in crate::build::shipped(root)?.programs {
+    for (dir, features) in crate::build::shipped(root)?.programs.into_iter().filter(|(d, _)| d.starts_with(root.join("userland"))) {
         let at = rel(root, &dir);
         let text = std::fs::read_to_string(dir.join("Cargo.toml"))
             .map_err(|e| format!("{at}/Cargo.toml: {e}"))?;

m4-no-shape:

diff --git a/src/userlandhost.rs b/src/userlandhost.rs
index 7cb912ba8..1fb9ed549 100644
--- a/src/userlandhost.rs
+++ b/src/userlandhost.rs
@@ -270,7 +270,7 @@ fn owed(root: &Path, issue: &str, app: &str) -> Result<(), String> {
         ["issues", area, slug] => word(area) && slug.strip_suffix(".md").is_some_and(word),
         _ => false,
     };
-    if !shaped {
+    if false && !shaped {
         return Err(format!("`issue` is {issue:?}, which is no issues/<area>/<slug>.md"));
     }
     let text = std::fs::read_to_string(root.join(issue))

m5-no-status:

diff --git a/src/userlandhost.rs b/src/userlandhost.rs
index 7cb912ba8..ca7b862fc 100644
--- a/src/userlandhost.rs
+++ b/src/userlandhost.rs
@@ -281,7 +281,7 @@ fn owed(root: &Path, issue: &str, app: &str) -> Result<(), String> {
         .into_iter()
         .flat_map(str::lines)
         .find_map(|line| line.strip_prefix("status: "));
-    if !matches!(status, Some("open" | "assigned")) {
+    if false && !matches!(status, Some("open" | "assigned")) {
         return Err(format!("{issue} is no work still owed: its status is {status:?}"));
     }
     if !text.contains(&format!("`{app}`")) {

m6-any-case:

diff --git a/src/userlandhost.rs b/src/userlandhost.rs
index 7cb912ba8..4c949ace1 100644
--- a/src/userlandhost.rs
+++ b/src/userlandhost.rs
@@ -225,7 +225,7 @@ fn declared(manifest: &toml::Value, root: &Path) -> Result<Host, String> {
             let case = exempt.as_table().filter(|t| t.len() == 1).and_then(|t| t.iter().next());
             match case {
                 Some((case, why))
-                    if ["owns", "manages"].contains(&case.as_str())
+                    if !case.is_empty()
                         && why.as_str().is_some_and(|why| !why.trim().is_empty()) =>
                 {
                     Ok(Host::Exempt)

m7-members-only:

diff --git a/src/build.rs b/src/build.rs
index f37b03422..0f0d5ac01 100644
--- a/src/build.rs
+++ b/src/build.rs
@@ -1109,7 +1109,7 @@ pub fn shipped(root: &Path) -> Result<Shipped, String> {
             ));
         }
         for c in config_crates(root, &config) {
-            if matches!(c.built, Built::Member | Built::Standalone) {
+            if matches!(c.built, Built::Member) {
                 programs.insert((c.dir.clone(), c.features));
             }
             crates.insert((c.dir, c.features));

The runners:

#!/bin/bash
# Each case: the measurement harness plus at most one mutation, applied as checked
# patches, built, run, and reverted; the tree is shown clean after each.
S=/private/tmp/claude-502/-Users-jan-Dev-jan-toyos/2280e09e-428b-4b81-bc00-1ede594b7247/scratchpad
cd /Users/jan/Dev/jan/toyos-hostapps || exit 2
for case in baseline m1-console-unexempt m2-no-skip; do
  patches=("$S/measure.patch")
  [ "$case" != baseline ] && patches+=("$S/$case.patch")
  before=$(git status --porcelain | wc -l | tr -d ' ')
  for p in "${patches[@]}"; do git apply --check "$p" && git apply "$p" || { echo "$case: $p does not apply"; exit 3; }; done
  cargo test --lib --no-run > "$S/ctl-$case-build.log" 2>&1; built=$?
  if [ $built = 0 ]; then
    cargo test --lib measure_apps -- --nocapture > "$S/ctl-$case.log" 2>&1; rc=$?
  else
    rc=build-failed
  fi
  for p in "${patches[@]}"; do git apply -R "$p"; done
  after=$(git status --porcelain | wc -l | tr -d ' ')
  echo "$case: build EXIT=$built run EXIT=$rc dirty-before=$before dirty-after=$after"
done
echo DONE
#!/bin/bash
# Each mutation applied as a checked patch, built, its tests run, and reverted.
S=/private/tmp/claude-502/-Users-jan-Dev-jan-toyos/2280e09e-428b-4b81-bc00-1ede594b7247/scratchpad
cd /Users/jan/Dev/jan/toyos-hostapps || exit 2
for m in m3-userland-only m4-no-shape m5-no-status m6-any-case m7-members-only; do
  before=$(git status --porcelain | wc -l | tr -d ' ')
  git apply --check "$S/$m.patch" && git apply "$S/$m.patch" || { echo "$m does not apply"; exit 3; }
  cargo test --lib --no-run > "$S/unit-$m-build.log" 2>&1; built=$?
  if [ $built = 0 ]; then
    cargo test --lib -- userlandhost every_modes_crates_and_init_ship > "$S/unit-$m.log" 2>&1; rc=$?
  else
    rc=build-failed
  fi
  git apply -R "$S/$m.patch"
  after=$(git status --porcelain | wc -l | tr -d ' ')
  echo "$m: build EXIT=$built run EXIT=$rc dirty-before=$before dirty-after=$after"
  grep -E "\.\.\. FAILED" "$S/unit-$m.log"
done
echo DONE

Their output:

baseline: build EXIT=0 run EXIT=0 dirty-before=0 dirty-after=0
m1-console-unexempt: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
m2-no-skip: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
DONE
m3-userland-only: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
test userlandhost::tests::every_program_the_images_ship_declares_what_it_is_to_a_host ... FAILED
m4-no-shape: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
test userlandhost::tests::a_host_declaration_is_read_whole_and_refused_by_name ... FAILED
m5-no-status: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
test userlandhost::tests::a_host_declaration_is_read_whole_and_refused_by_name ... FAILED
m6-any-case: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
test userlandhost::tests::a_host_declaration_is_read_whole_and_refused_by_name ... FAILED
m7-members-only: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
test build::tests::every_modes_crates_and_init_ship ... FAILED
DONE

@Japabu Japabu changed the title Every userland app builds on Linux, macOS and Windows on each pull request, or its manifest says why it cannot Every app the images ship is checked for Linux, macOS and Windows, or its manifest says why it cannot Oct 1, 2026
@Japabu

Japabu commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Review of 33f357f, round 2, against git diff b3a238cc7..33f357f27 (both merges clean: git show --remerge-diff empty). CI: host success, run 36873717047 at this head ("[ci] Host: 59 step(s), all green"); every_program_the_images_ship_declares_what_it_is_to_a_host, a_host_declaration_is_read_whole_and_refused_by_name and every_modes_crates_and_init_ship ran ok in it. No hardware target.

Round 1's BLOCKERs

  1. pkg-config — CLOSED. Run 36873717047's log has no alsa line and no doom or toybox unit. Its one pkg-config line is Compiling pkg-config v0.3.32 in calc's survey step, which main already runs. The only user of that crate there is wayland-sys 0.31.11, which returns before probing when dlopen is on (build.rs:4-7).
  2. doomgeneric fetch — CLOSED. No doom unit compiles in the run, so its build.rs never runs.
  3. Two judges — CLOSED. Each host gets one command on every runner. The same 11 apps pass on ubuntu-24.04 (the run) and on the macOS dev host (PR body, EXIT=0). That an app which compiles C or probes pkg-config cannot pass a cross-check is recorded in issues/build/no-app-is-built-for-a-host-each-is-only-checked.md.
  4. Second list — CLOSED. m3 and m7 are red, and each apps step checks toyos-ld.
  5. filepicker — CLOSED. Its exemption is gone, each apps step checks it, and the issue records that it runs on no host.
  6. Criterion — CLOSED. owns or manages reads the same in src/userlandhost.rs:228, the module doc, reviewer.md's Hosts bullet and userland/CLAUDE.md:5. m6 is red.
  7. socket2 — CLOSED. 0b238fb's one line gives use crate::MsgHdrMut (src/socket.rs:24) the same cfg as recvmsg (:635): upstream v0.6.3's all(unix, not(target_os = "redox")) at both sites, plus target_os = "toyos". 0b238fb is the toyos branch head, and userland/Cargo.lock:3804 is the only lockfile that names the branch. The PR body's measurement: 101 at 48d15f5, 0 at 0b238fb.
  8. Check-only gap — CLOSED. The new issue records it with an owner and an exit. Its Windows half is a NOTE below.

Round 1's NOTEs are all answered at this head except #673 and the Windows issue, both carried below. Its four REMOVEs are gone.

BLOCKER

  1. src/ci.rs:569 — nothing tests the per-host skip — nothing committed calls apps_for, and every fails in the tree names all three hosts. So this mutation stays green in every test and in CI: - if fails.contains(&os) { / + if !fails.is_empty() {. Under it, an app declaring fails = ["windows"] also goes unchecked on Linux and macOS. Round 1's a_build_and_its_declaration_disagreeing_is_red_both_ways held exactly this case ("it fails on Windows alone") and was cut with judge: a weaker check bought with size. The test it must turn red, with no cargo run: a program whose fails is [Windows] is attempted for linux and macos, and skipped for windows.
  2. src/userlandhost.rs:16-17, src/ci.rs:569-572 — the stale-fails trade is recorded only in the PR body's Unsure — a fails entry that outlives its fix keeps that host unjudged while claiming the app fails there. That is the calc incident's shape, and no issue holds it with an owner and an exit. The nightly could attempt the declared hosts at no new cost on macos-latest: --build-only has already fetched doomgeneric into that checkout, and pkg-config 0.3.32 refuses a cross target before it runs (src/lib.rs:582-583). It cannot do so soundly while every runner checks every host. Once doom's build.rs compiles with the host's C compiler, doom checks green for macos there and red in the ubuntu PR runner's cross-check. The ratchet would then red for a removal the PR gate refuses: round 1's BLOCKER 3 deadlock. The ratchet belongs at the new issue's exit, one judge per host. Record it there, as one more unseen item with an exit that attempts each declared host on its own runner and reds when one compiles, or in an issue of its own.

NOTE

  • Net +420 (+458 −38). src production +251 −5 (userlandhost.rs +183 −2, ci.rs +58 −1, build.rs +10 −2), tests +79, manifests +53, issues +59 −25, prompts and docs +15 −7, lockfile 1. The growth is the gate the owner asked for, and it is accepted; round 1's named deletions are made.
  • Linux (question 3) — still built, linked and run. The survey step (src/ci.rs:521-531, unchanged from main) ran cargo test --manifest-path userland/calc/Cargo.toml --target x86_64-unknown-linux-gnu green in run 36873717047, and did the same for blockd, fsd, logd, netd, pkg, soundd and sshd. toyos-ld links in "the host workspace" step (root member, Cargo.toml:41). What nothing links for Linux is editor, filepicker, files, host, input-test, paint and snake: round 1 linked all of them but filepicker, and main linked none. The reason given for dropping the link is that it "would give Linux a verdict that the macOS runner's check does not give". That held only beside the ratchet. With declared hosts skipped on every runner, a red seen only at link time is answered by fails and deadlocks nothing, and build scripts already let the runners differ. A cargo build where the runner is the host costs only cc, which is declared for Linux (issues/build/the-build-runs-host-tools-outside-rust-and-qemu.md:22), and it is what the new issue's exit asks for Linux.
  • test-runner and metalprobe (question 4) — nothing judges them now. Only tests/*case/system.toml carry them, so programs never reads their manifests, and nothing read their exempt lines either. It does not matter, because neither is an app. test-runner runs a guest's job list and stops the machine. metalprobe drives a metal boot's framebuffer and USB stick (tests/metaldevicecase/system.toml:44-53). No mode's image hands either to a user. The edge left is a program that only a case config carries, and the licence gate (build::shipped) has the same edge.
  • issues/build/the-build-system-does-not-compile-on-windows.md (question 5) — needs three things:
    1. An Owner:: its four Unix-only sites, src/buildlock.rs, src/toolchain.rs, src/worktree.rs and src/tether.rs, held by the orchestrator.
    2. An **Exit:** that a run can fail: cargo run -- --ci host green on a Windows runner in ci.yml. That is the leg the new issue's Windows exit waits for. The cross cargo check the file now names as its judge does not count, because the same file says a green Windows compile "would say nothing about a working Windows build".
    3. Its symlink question ("either ToyOS grows symbolic links, or…", which settles two of the seven errors) moved into a kind: question, status: owner file, or ruled on. The question is the owner's, and while it sits inside a tooling file it blocks the rest.
      The file is pre-existing and not this PR's. Until it moves, neither the new issue's Windows exit nor doom's windows entry has a reachable end.
  • Rules and records: the ABI and kernel rulings, the no-panic track, the owner's 2026-09-30 rulings, CPU microcode, the crate track, the LLVM-bytes and TCO-overflow defects #673 — still open. It still edits reviewer.md's Fit bullet and implementer.md's dependency line, the same lines this branch edits. Its two src/build.rs hunks are doc comments (its lines 368 and 598) and do not overlap Shipped. Whichever lands second carries both.

REMOVE

SEND BACK

…where the gate runs on it, and a stale `fails` is recorded

`apps_for`'s two per-host decisions are pure functions now, `attempted`
and `verb`, and `an_app_is_judged_for_every_host_its_fails_does_not_name`
holds both with no cargo run: an app whose `fails` is `[windows]` is
judged for Linux and macOS and skipped for Windows, an exempt program is
judged for none, and on a Linux host Linux's apps are built and the other
two hosts' checked. Nothing tested the skip before: every `fails` in the
tree names all three hosts, so `if !fails.is_empty()` in place of
`if fails.contains(&os)` stayed green everywhere.

Where the gate runs on the host it judges, it builds: `cargo build
--target <triple>` when the triple is the host's own, and `cargo check
--target <triple>` elsewhere. On ubuntu-24.04 that links Linux's apps on
every pull request, and on the nightly's macOS runner, macOS's. Round 2
dropped the link so that a host would have one verdict on every runner,
which matters only beside a ratchet. A declared host is attempted on no
runner, so a failure only one runner sees is answered by `fails` and
deadlocks nothing. On the macOS dev host, from an empty target directory,
checking the 11 apps for aarch64-apple-darwin took 20 s and 443 MB, and
building them 26 s and 1.2 GB, exit 0 each.

A `fails` entry that outlives its fix keeps that host unjudged while it
claims the app fails there. Attempting declared hosts, red when one
builds, is not sound while two runners judge one host. For example, once
doom's build.rs compiles with the host's C compiler, doom builds for
macOS on the macOS runner and fails the Linux runner's check of macOS.
Its `fails` entry would then be red whether it stays or goes. The gap is
recorded where the other gaps of judging a host from another host's
runner are. That issue was `no-app-is-built-for-a-host-each-is-only-checked`,
and its title stopped being true when Linux's apps began to be built. It
is now `a-hosts-apps-are-judged-on-other-hosts-runners`, and its exit
attempts each declared host on that host's own runner.

Deleted as the review asked:
- "the same on whichever host runs it", which a build script that probes
  the host that checks makes false;
- the served port in the exemptions of blockd, compositor, logd, netd and
  soundd. filepicker serves a port and is an app, so the device or
  SysCap claim alone carries each exemption;
- "as the system servers do," in `userland/CLAUDE.md`, since filepicker
  is a system server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
@Japabu Japabu changed the title Every app the images ship is checked for Linux, macOS and Windows, or its manifest says why it cannot Every app the images ship is built or checked for Linux, macOS and Windows, or its manifest says why it cannot Oct 1, 2026
@Japabu

Japabu commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Answers to the review of 33f357f, at 207bdde.

BLOCKERs

  1. The per-host skip is tested now. apps_for makes two per-host decisions, and both are now pure functions in src/ci.rs:

    • attempted splits the apps judged for a host from those whose fails names it;
    • verb picks build or check (the Linux NOTE below).

    an_app_is_judged_for_every_host_its_fails_does_not_name covers both, with no cargo run:

    • an app whose fails is [windows] is judged for linux and macos and skipped for windows;
    • an exempt program is judged for none;
    • an app that declares nothing is judged for all three.

    The review's mutation, if fails.contains(&os) → if !fails.is_empty(), is applied in attempted, where the skip now lives. It builds (EXIT=0), the test exits 101 with "left: (["calc"], ["doom"]) right: (["calc", "doom"], [])", and the tree is clean before and after. The patch is below.

  2. Stale fails is recorded in issues/build/a-hosts-apps-are-judged-on-other-hosts-runners.md, as the third thing that goes unseen, with an owner.

    • Exit: on each pull request, a runner of each host builds that host's apps, judges no other host's, and attempts each app whose fails names its host. It is red when one of those builds.
    • The module doc cites the issue where it describes fails and the verdict.
    • The file was no-app-is-built-for-a-host-each-is-only-checked.md. Once Linux's apps are built, that title was false, so the slug moved.

    The brief's first option is a nightly that attempts only the declared hosts. It is not taken, because both nightly runners that run host, ubuntu-24.04 and macos-latest, judge every host. A ratchet on either one deadlocks against the other's check:

    • On macos-latest: once doom's build.rs compiles with the host's C compiler, doom builds for macOS there. It still fails ubuntu's cross-check of macOS. The ratchet reds while macos stays in doom's fails, and the PR gate reds once it goes.
    • On ubuntu-24.04: take an app whose build script probes pkg-config, as cpal's alsa-sys does in toybox and doom. Once the library is installed there, it passes ubuntu's native build. It still fails macOS's cross-check, because pkg-config 0.3.32 refuses a cross target. On ubuntu the ratchet would also bring back the pkg-config probe and the doomgeneric fetch, round 1's BLOCKERs 1 and 2, in the nightly's host job.

    Nothing short of attempting the app tells that a declaration is stale.

NOTEs

  • Size: +512 −38 against origin/main, which is +54 over round 2.
    • +25 is the new test.
    • +23 is src/ci.rs production: verb, attempted, and apps_for's signature.
    • +5 is the issue.
    • +1 is the module doc.
  • Linux (question 3): fixed. Where the gate runs on a host's own triple, it runs cargo build --target <triple> instead of cargo check.
    • On ubuntu-24.04 that builds and links Linux's 11 apps on every pull request. On the nightly's macos-latest it does the same for macOS's.
    • Linking adds no host tool on Linux, since cc is declared there.
    • On the dev host, from an empty target directory, checking the 11 apps for aarch64-apple-darwin took 20 s and 443 MB, and building them took 26 s and 1.2 GB, exit 0 each. CI on this push is the first measure of the Linux build.
    • The body no longer says that Linux is unlinked, or that the verdict is the same on every runner.
  • test-runner and metalprobe (question 4): no change. Neither is an app, as the review says. A program that only a case config carries is judged by neither this gate nor the licence gate.
  • issues/build/the-build-system-does-not-compile-on-windows.md (question 5): not touched. It is not this PR's file, and issues/README.md says not to touch an issue you do not own. The owner, the exit and the symlink question need a brief for its owner. Until then, the new issue's Windows exit and doom's windows entry have no reachable end, as the review says. The body's Unsure says the same.
  • Rules and records: the ABI and kernel rulings, the no-panic track, the owner's 2026-09-30 rulings, CPU microcode, the crate track, the LLVM-bytes and TCO-overflow defects #673: still open; gh pr view 673 says OPEN. This round touches neither implementer.md nor reviewer.md. Whichever lands second carries Rules and records: the ABI and kernel rulings, the no-panic track, the owner's 2026-09-30 rulings, CPU microcode, the crate track, the LLVM-bytes and TCO-overflow defects #673's deletion and BLOCKER line, and this branch's dependency rule.

REMOVEs, all deleted:

  • "the same on whichever host runs it" in src/userlandhost.rs. The issue file's copy went with its rewrite, and the body's copy is gone too.
  • the served port in the exemptions of blockd, compositor, logd, netd and soundd. Each now names only its device or SysCap claim.
  • "as the system servers do," in userland/CLAUDE.md:5.
  • the body's "## Answers to the review of 16ba295". These answers are this comment, not the body.

The controls, as checked patches, each applied to 207bdde by the runners below, built, run and reverted, with the tree clean before and after.

The review's mutation, m-skip-any:

diff --git a/src/ci.rs b/src/ci.rs
index cf6beba9c..f42980b34 100644
--- a/src/ci.rs
+++ b/src/ci.rs
@@ -608,7 +608,7 @@ fn attempted(programs: &[Program], os: Os) -> (Vec<&Program>, Vec<&str>) {
     let (mut attempted, mut declared) = (Vec::new(), Vec::new());
     for program in programs {
         let Host::App(fails) = &program.host else { continue };
-        if fails.contains(&os) {
+        if !fails.is_empty() {
             declared.push(program.dir.as_str());
         } else {
             attempted.push(program);

The new verb decision, m-verb-swapped:

diff --git a/src/ci.rs b/src/ci.rs
index cf6beba9c..41e177902 100644
--- a/src/ci.rs
+++ b/src/ci.rs
@@ -595,7 +595,7 @@ fn apps_for(
 
 /// `build` where the gate runs on `os`'s own triple, and `check` elsewhere.
 fn verb(os: Os, host_triple: &str) -> &'static str {
-    if os.triple() == host_triple {
+    if os.triple() != host_triple {
         "build"
     } else {
         "check"

Round 2's controls ran again here, because apps_for changed under them. m1-console-unexempt and m3–m7 are round 2's patches, unchanged, and each applies with git apply --check. The measurement harness takes the host triple now, and the skip moved into attempted, so those two are new. measure3:

--- a/src/ci.rs
+++ b/src/ci.rs
@@ -882,6 +882,19 @@
 mod tests {
     use super::*;
 
+    #[test]
+    fn measure_apps() {
+        let root = Path::new(env!("CARGO_MANIFEST_DIR"));
+        let programs = crate::userlandhost::programs(root).expect("programs");
+        let host = crate::toolchain::host_triple();
+        let verdicts: Vec<_> =
+            Os::ALL.into_iter().map(|os| (os, apps_for(root, &programs, os, &host))).collect();
+        for (os, verdict) in &verdicts {
+            eprintln!("MEASURE {os:?}: {verdict:?}");
+        }
+        assert!(verdicts.iter().all(|(_, v)| v.is_ok()), "a host is red");
+    }
+
     /// A deterministic control on `host`'s `std::env::set_var("TMPDIR", ...)`:
     /// delete that line and every child writes to the real `$TMPDIR` instead of
     /// this job's private one, which never gains the lock a real step always

m2-no-skip3:

--- a/src/ci.rs
+++ b/src/ci.rs
@@ -608,7 +608,7 @@
     let (mut attempted, mut declared) = (Vec::new(), Vec::new());
     for program in programs {
         let Host::App(fails) = &program.host else { continue };
-        if fails.contains(&os) {
+        if false && fails.contains(&os) {
             declared.push(program.dir.as_str());
         } else {
             attempted.push(program);

The runners:

#!/bin/bash
# Each mutation applied as a checked patch to the committed tree, built, the
# per-host test run, and reverted; the tree is shown clean before and after.
S=/private/tmp/claude-502/-Users-jan-Dev-jan-toyos/2280e09e-428b-4b81-bc00-1ede594b7247/scratchpad/r3
cd /Users/jan/Dev/jan/toyos-hostapps || exit 2
TEST=ci::tests::an_app_is_judged_for_every_host_its_fails_does_not_name
for m in m-skip-any m-verb-swapped; do
  before=$(git status --porcelain | wc -l | tr -d ' ')
  git apply --check "$S/$m.patch" && git apply "$S/$m.patch" || { echo "$m does not apply"; exit 3; }
  cargo test --lib --no-run > "$S/$m-build.log" 2>&1; built=$?
  if [ $built = 0 ]; then
    cargo test --lib -- --exact "$TEST" > "$S/$m.log" 2>&1; rc=$?
  else
    rc=build-failed
  fi
  git apply -R "$S/$m.patch"
  after=$(git status --porcelain | wc -l | tr -d ' ')
  echo "$m: build EXIT=$built run EXIT=$rc dirty-before=$before dirty-after=$after"
  grep -E "\.\.\. FAILED|panicked|left:|right:" "$S/$m.log"
done
cargo test --lib -- --exact "$TEST" > "$S/unmutated.log" 2>&1; echo "unmutated: run EXIT=$? dirty=$(git status --porcelain | wc -l | tr -d ' ')"
echo DONE
#!/bin/bash
# Round 2's controls at this head: the end-to-end measurement under no mutation,
# console unexempted and the skip removed; then the five reader mutations. Each
# patch is checked, applied, built, run and reverted; the tree is shown clean.
R=/private/tmp/claude-502/-Users-jan-Dev-jan-toyos/2280e09e-428b-4b81-bc00-1ede594b7247/scratchpad
S=$R/r3
cd /Users/jan/Dev/jan/toyos-hostapps || exit 2
for case in baseline m1-console-unexempt m2-no-skip3; do
  patches=("$S/measure3.patch")
  [ "$case" = m1-console-unexempt ] && patches+=("$R/m1-console-unexempt.patch")
  [ "$case" = m2-no-skip3 ] && patches+=("$S/m2-no-skip3.patch")
  before=$(git status --porcelain | wc -l | tr -d ' ')
  for p in "${patches[@]}"; do git apply --check "$p" && git apply "$p" || { echo "$case: $p does not apply"; exit 3; }; done
  cargo test --lib --no-run > "$S/ctl-$case-build.log" 2>&1; built=$?
  if [ $built = 0 ]; then
    cargo test --lib measure_apps -- --nocapture > "$S/ctl-$case.log" 2>&1; rc=$?
  else
    rc=build-failed
  fi
  for p in "${patches[@]}"; do git apply -R "$p"; done
  after=$(git status --porcelain | wc -l | tr -d ' ')
  echo "$case: build EXIT=$built run EXIT=$rc dirty-before=$before dirty-after=$after"
  grep "^MEASURE" "$S/ctl-$case.log"
done
for m in m3-userland-only m4-no-shape m5-no-status m6-any-case m7-members-only; do
  before=$(git status --porcelain | wc -l | tr -d ' ')
  git apply --check "$R/$m.patch" && git apply "$R/$m.patch" || { echo "$m does not apply"; exit 3; }
  cargo test --lib --no-run > "$S/unit-$m-build.log" 2>&1; built=$?
  if [ $built = 0 ]; then
    cargo test --lib -- userlandhost every_modes_crates_and_init_ship > "$S/unit-$m.log" 2>&1; rc=$?
  else
    rc=build-failed
  fi
  git apply -R "$R/$m.patch"
  after=$(git status --porcelain | wc -l | tr -d ' ')
  echo "$m: build EXIT=$built run EXIT=$rc dirty-before=$before dirty-after=$after"
  grep -E "\.\.\. FAILED" "$S/unit-$m.log"
done
echo DONE

Their output:

m-skip-any: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
test ci::tests::an_app_is_judged_for_every_host_its_fails_does_not_name ... FAILED
thread 'ci::tests::an_app_is_judged_for_every_host_its_fails_does_not_name' (68136237) panicked at src/ci.rs:954:9:
  left: (["calc"], ["doom"])
 right: (["calc", "doom"], [])
m-verb-swapped: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
test ci::tests::an_app_is_judged_for_every_host_its_fails_does_not_name ... FAILED
thread 'ci::tests::an_app_is_judged_for_every_host_its_fails_does_not_name' (68138913) panicked at src/ci.rs:957:9:
  left: ["check", "build", "build"]
 right: ["build", "check", "check"]
unmutated: run EXIT=0 dirty=0
DONE
baseline: build EXIT=0 run EXIT=0 dirty-before=0 dirty-after=0
MEASURE Linux: Ok("11 app(s) pass `cargo check --target x86_64-unknown-linux-gnu`; userland/doom, userland/proctest, userland/shell, userland/terminal, userland/toybox not attempted, as their manifests declare")
MEASURE Macos: Ok("11 app(s) pass `cargo build --target aarch64-apple-darwin`; userland/doom, userland/proctest, userland/shell, userland/terminal, userland/toybox not attempted, as their manifests declare")
MEASURE Windows: Ok("11 app(s) pass `cargo check --target x86_64-pc-windows-msvc`; userland/doom, userland/proctest, userland/shell, userland/terminal, userland/toybox not attempted, as their manifests declare")
m1-console-unexempt: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
MEASURE Linux: Err("userland/console fails for linux and declares neither `fails` there nor `exempt`: cargo check --manifest-path userland/console/Cargo.toml --target x86_64-unknown-linux-gnu exited exit status: 101")
MEASURE Macos: Err("userland/console fails for macos and declares neither `fails` there nor `exempt`: cargo build --manifest-path userland/console/Cargo.toml --target aarch64-apple-darwin exited exit status: 101")
MEASURE Windows: Err("userland/console fails for windows and declares neither `fails` there nor `exempt`: cargo check --manifest-path userland/console/Cargo.toml --target x86_64-pc-windows-msvc exited exit status: 101")
m2-no-skip3: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
MEASURE Linux: Err("userland/doom fails for linux and declares neither `fails` there nor `exempt`: cargo check --manifest-path userland/doom/Cargo.toml --target x86_64-unknown-linux-gnu exited exit status: 101; userland/proctest fails for linux and declares neither `fails` there nor `exempt`: cargo check --manifest-path userland/proctest/Cargo.toml --target x86_64-unknown-linux-gnu exited exit status: 101; userland/shell fails for linux and declares neither `fails` there nor `exempt`: cargo check --manifest-path userland/shell/Cargo.toml --target x86_64-unknown-linux-gnu exited exit status: 101; userland/terminal fails for linux and declares neither `fails` there nor `exempt`: cargo check --manifest-path userland/terminal/Cargo.toml --target x86_64-unknown-linux-gnu exited exit status: 101; userland/toybox fails for linux and declares neither `fails` there nor `exempt`: cargo check --manifest-path userland/toybox/Cargo.toml --target x86_64-unknown-linux-gnu exited exit status: 101")
MEASURE Macos: Err("userland/doom fails for macos and declares neither `fails` there nor `exempt`: cargo build --manifest-path userland/doom/Cargo.toml --target aarch64-apple-darwin exited exit status: 101; userland/proctest fails for macos and declares neither `fails` there nor `exempt`: cargo build --manifest-path userland/proctest/Cargo.toml --target aarch64-apple-darwin exited exit status: 101; userland/shell fails for macos and declares neither `fails` there nor `exempt`: cargo build --manifest-path userland/shell/Cargo.toml --target aarch64-apple-darwin exited exit status: 101; userland/terminal fails for macos and declares neither `fails` there nor `exempt`: cargo build --manifest-path userland/terminal/Cargo.toml --target aarch64-apple-darwin exited exit status: 101; userland/toybox fails for macos and declares neither `fails` there nor `exempt`: cargo build --manifest-path userland/toybox/Cargo.toml --target aarch64-apple-darwin exited exit status: 101")
MEASURE Windows: Err("userland/doom fails for windows and declares neither `fails` there nor `exempt`: cargo check --manifest-path userland/doom/Cargo.toml --target x86_64-pc-windows-msvc exited exit status: 101; userland/proctest fails for windows and declares neither `fails` there nor `exempt`: cargo check --manifest-path userland/proctest/Cargo.toml --target x86_64-pc-windows-msvc exited exit status: 101; userland/shell fails for windows and declares neither `fails` there nor `exempt`: cargo check --manifest-path userland/shell/Cargo.toml --target x86_64-pc-windows-msvc exited exit status: 101; userland/terminal fails for windows and declares neither `fails` there nor `exempt`: cargo check --manifest-path userland/terminal/Cargo.toml --target x86_64-pc-windows-msvc exited exit status: 101; userland/toybox fails for windows and declares neither `fails` there nor `exempt`: cargo check --manifest-path userland/toybox/Cargo.toml --target x86_64-pc-windows-msvc exited exit status: 101")
m3-userland-only: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
test userlandhost::tests::every_program_the_images_ship_declares_what_it_is_to_a_host ... FAILED
m4-no-shape: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
test userlandhost::tests::a_host_declaration_is_read_whole_and_refused_by_name ... FAILED
m5-no-status: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
test userlandhost::tests::a_host_declaration_is_read_whole_and_refused_by_name ... FAILED
m6-any-case: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
test userlandhost::tests::a_host_declaration_is_read_whole_and_refused_by_name ... FAILED
m7-members-only: build EXIT=0 run EXIT=101 dirty-before=0 dirty-after=0
test build::tests::every_modes_crates_and_init_ship ... FAILED
DONE

@Japabu

Japabu commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Review of 207bdde, round 3, against git diff 33f357f27..207bdde97. CI: host pass, run 36880870930 at this head, 12m46s ("[ci] Host: 59 step(s), all green"). an_app_is_judged_for_every_host_its_fails_does_not_name, every_program_the_images_ship_declares_what_it_is_to_a_host, a_host_declaration_is_read_whole_and_refused_by_name and every_modes_crates_and_init_ship ran ok in it. No hardware target.

Round 2's BLOCKERs

  1. The per-host skip was untested — CLOSED.
    • The skip is now attempted (src/ci.rs:607-618).
    • an_app_is_judged_for_every_host_its_fails_does_not_name (src/ci.rs:939-958) checks, with no cargo run, that an app with fails = [Windows] is attempted for linux and macos and declared for windows, and that an exempt program is in neither list.
    • With the review's patch in attempted, the test exits 101: "left: (["calc"], ["doom"]) right: (["calc", "doom"], [])". Without it, 0. The tree was clean before and after (comment 5934119603).
  2. A stale fails entry was recorded nowhere — CLOSED.
    • issues/build/a-hosts-apps-are-judged-on-other-hosts-runners.md:21-25 records it, with an owner (:27) and an exit (:29-33). src/userlandhost.rs:25-29 cites it.
    • git grep no-app-is-built-for-a-host-each-is-only-checked HEAD exits 1, so no citation of the old name is left.
    • The reason the ratchet waits holds where a fix passes on one runner and fails the other's judgment of the same host. Two cases do: doom's C, and alsa-sys's pkg-config, which refuses a cross target.

BLOCKER

None.

NOTE

  • Size: net +474 (+512 −38, git diff --shortstat origin/main...HEAD).
    • src production: +275 −5 (userlandhost.rs +184 −2, ci.rs +81 −1, build.rs +10 −2).
    • tests +104, manifests +53, issues +64 −25, prompts and docs +15 −7, lockfile 1.
    • Round 3 adds +54 net: the extraction and test round 2 asked for, and the issue. Accepted.
  • src/ci.rs:570 — the Linux build is back (the brief's question).
    • Run 36880870930's "the apps for linux" reads "11 app(s) pass cargo build --target x86_64-unknown-linux-gnu". It has 11 Finished lines: toyos-ld, calc, editor, filepicker, files, host, input-test, paint, pkg, snake and sshd. The step took 26 s, against 34 s for round 2's check (run 36873717047).
    • calc's tests run on Linux in the same job: "userland/calc: cargo test --manifest-path userland/calc/Cargo.toml --target x86_64-unknown-linux-gnu" is a green step.
    • The run has no alsa line and downloads no doomgeneric. Its one pkg-config line is calc's Compiling pkg-config v0.3.32, as on main.
    • Linking uses cc, which issues/build/the-build-runs-host-tools-outside-rust-and-qemu.md:22 declares.
  • issues/build/a-hosts-apps-are-judged-on-other-hosts-runners.md:29-33 — the exit (the brief's question).
    • It names a check that can fail: once each host has a runner that judges it alone, a declared app that builds goes red.
    • Until then nothing reads the file, and its close is held by review alone, as for every tooling issue.
    • Windows rests on issues/build/the-build-system-does-not-compile-on-windows.md, which still has no owner and no exit.
    • The exit attempts doom and toybox on each pull request, on each host's runner. That brings back alsa-sys's pkg-config probe on Linux and doom's doomgeneric fetch, round 1's BLOCKERs 1 and 2.
    • On Linux both apps need ALSA's alsa.pc, which ubuntu-24.04 lacks (run 36865166000: "pkg-config exited with status code 1", "The file alsa.pc needs to be installed"). So there the ratchet cannot go red for them, whatever their source does.
    • The issue names neither prerequisite.
  • src/ci.rs:586 — no committed test holds the gate's red.
    • - return Err(red.join("; ")); / + return Ok(red.join("; ")); stays green in every test and in the host job.
    • Only the uncommitted harness showed the red (m1-console-unexempt, comment 5934119603).
    • If apps_for took its cargo runner as a closure, one test with no cargo run could hold the red, and the skip and the verb as apps_for uses them.
  • src/ci.rs:536 — no test holds the Linux build. apps_for(root, &programs, os, "") checks Linux again and stays green everywhere. Only the step's text, cargo check --target x86_64-unknown-linux-gnu, would show it.
  • Rules and records: the ABI and kernel rulings, the no-panic track, the owner's 2026-09-30 rulings, CPU microcode, the crate track, the LLVM-bytes and TCO-overflow defects #673 — still OPEN. It edits reviewer.md's Fit bullet and implementer.md's dependency line, as this branch does. Whichever lands second carries both.

REMOVE

  • PR body, Gates — "This push's CI run is the first measure of the Linux build on ubuntu-24.04." — dated to one push; run 36880870930 has since measured it.
  • PR body — ", before this round" — a review round's history in main's record.

LAND AFTER NAMED CHANGES

@Japabu
Japabu added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit 1a8cd4a Oct 1, 2026
1 check passed
@Japabu
Japabu deleted the wt/toyos-hostapps branch October 1, 2026 15:40
Japabu added a commit that referenced this pull request Oct 1, 2026
Two hunks conflict, and each keeps both sides' changes.

- `implementer.md`: this branch drops the ABI-brief sentence, and #674's
  dependency sentence replaces "No new dependency.".
- `reviewer.md`'s Fit line: this branch's two BLOCKER clauses stay, and
  #674's dependency clause replaces "No new dependency or fetch.".

The rest of #674 merged clean. `git diff origin/main -- .claude
userland/CLAUDE.md` shows only this branch's own changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Japabu added a commit that referenced this pull request Oct 1, 2026
src/ci.rs conflicted twice, both sides additive, and both are kept
whole: the `use` of `cicache::{self, Start}` beside #674's
`userlandhost::{Host, Os, Program}`, and `carry()` beside #674's
`apps_for`, `verb` and `attempted`. The merged file's changed lines
against main are exactly the branch's, and against the branch exactly
main's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant