Skip to content

Three audit claims traced: a BAR asked for twice panics the kernel, netstack keeps a dead client's socket, and a PCI call's slot is not its claim - #755

Merged
Japabu merged 5 commits into
mainfrom
wt/toyos-audit-devices
Oct 8, 2026
Merged

Japabu merged 5 commits into
mainfrom
wt/toyos-audit-devices

Conversation

@Japabu

@Japabu Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

An outside static audit of main at b432ed21c claimed three defects in the PCI claim substrate and in netstack. Each was argued from reading and run by nobody. This traces each in the tree, runs the two a test can reach, and records all three. It fixes none: the diff against main is three files under issues/.

Head 23f36bce9, on main 017451624.

Verdicts

claim verdict issue
a later BAR request panics on the cached retired object REPRODUCED issues/a-claims-bar-asked-for-again-after-its-handle-closed-panics-the-kernel.md
netstack keeps the TCP socket of a finished bridge REPRODUCED issues/netstack-keeps-the-socket-of-a-connection-whose-client-sent-no-close.md
a PCI call keeps only a reusable slot number across its claim's release NOT REPRODUCED: not run, and no step refuted issues/a-pci-call-in-flight-names-its-function-by-a-slot-the-next-claim-reuses.md

The two red tests, and where they are

The tree carries no known-red test: a red test is deleted, and its issue names the commit that restores it. So each test is one commit of this branch and its deletion another:

test added deleted restore
bar_map_again ef41f0ad1 a86cafa06 git revert a86cafa06
netstack_socket_churn 06bbc236d 8dd55d06a git revert 8dd55d06a

Each revert was applied alone on the tip and applies clean.

Both were run at 06bbc236d, the commit that carries both:

  • cargo test --test toyos-build -- bar_map_again: exit 1.
    [ 2.609 cpu0 kernel alert] PANIC: panicked at src/object/handle.rs:108:9:
    a handle to a retired SharedMem (koid 197)
    
    after the job's bar_map_again: BAR 4 asked for and its handle closed; asking again.
  • cargo test --test toyos-build -- netstack_socket_churn: exit 1.
    netstack_socket_churn: 4 connections ended and let go; netstack held 0 stream(s) before them and holds 4 after
    
  • The control for the second, the same test with the client's close request sent before it drops its pipes (patch in the first comment): exit 0. The red is the missing request and nothing else about the run.
  • cargo test --test toyos-checks with both tests in the tree: exit 0, 36 passed.

There is no control for the first short of a fix: its green arm is a kernel that answers.

Why these are guest tests

  • bar_map_again: the path is pcidev::bar_object into ops::install, both in kernel/src, which has no host build; no type refuses it today. A metal row reaches it through the T14's I219, but its red is that machine in a panic, and the T14 is not this agent's to run.
  • netstack_socket_churn: the state is Netstack's in userland/netstack/src/main.rs, a binary whose manifest exempts it from host builds and whose bridge reads kernel pipes; it has no host test to extend. It needs a real stack with a peer that ends connections, which under QEMU is a listener on the host's loopback.

The one not run

Every step of the third claim holds on reading: pci_slot answers a bare slot number and lets go of the claim; with_bound checks that the slot is bound and unfaulted, not whose binding it is; the two are under different locks; slot::reserve hands the first free slot to the next claim. Nothing found stops it.

It is not run because a guest cannot order a release and a second claim between two adjacent statements of one syscall. A test that does not depend on luck needs the kernel to hold a call at that point, as the existing SYS_DEBUG actuators hold other windows. That is kernel source, outside a change that touches issues/ and tests only, and a timing race in its place would be a flaky test. The issue's exit names that test.

Gates

  • cargo run -- --ci host at 23f36bce9: exit 0, Host: 77 step(s), all green.
  • No guest test is reached by the head's diff, which is issues/ alone.

Unsure of

  • The second way the first defect's object is retired, a first install refused for a full handle table, is read from the code and not run.
  • The audit's report is a local file outside git. Nothing of its text is in the tree or here; the issues are written from the trace and the runs.
  • a-pci-call-in-flight… is filed as defect though unrun, as netstack-removes-a-closed-stream-before-its-fin-leaves.md is: an isolation finding is not left as a finding.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A

Japabu and others added 5 commits October 8, 2026 09:56
… red on a kernel panic

A guest job claims QEMU's virtio NIC on tests/testcases, asks for one of its
memory BARs, closes the handle it is given and asks for the same BAR again on
the same live claim. The kernel owes it a handle or a refusal. At b432ed2 it
panics instead, `a handle to a retired SharedMem`, at
kernel/src/object/handle.rs's constructor: the claim's cached BAR object was
retired when its only handle closed, and the second request installs it again.

`cargo test --test toyos-build -- bar_map_again` exits 1 on that panic. The
test is red by design and is deleted by a later commit of this branch, with
its issue naming the revert that restores it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…d on a count that never returns

A guest job on tests/netcase dials a host server four times through netstack.
The host ends each connection at once; the job reads the end of stream, drops
both pipe ends without a close request, and waits for netstack's own
`piped.live` to return to what it was. Then it compares `sockets.tcp` with its
reading before the first connection. At b432ed2 that count is 0 before and 4
after: `bridge_piped` lets the bridge go and keeps the socket and its table
entry.

tests/netcase's runner is given the `netstack` connector so its job can dial
and read the counts.

`cargo test --test toyos-build -- netstack_socket_churn` exits 1. With the
close request added before the drop the same run exits 0, so the red is the
missing request alone. The test is red by design and is deleted by a later
commit of this branch, with its issue naming the revert that restores it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
A red test is deleted with its issue rather than carried: the guest binary,
its RUST_SKIP row, its registration, its dispatch arm and body, and the
`netstack` connector tests/netcase's runner was given for it.

Reverting this commit restores the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
A red test is deleted with its issue rather than carried: the guest binary,
its RUST_SKIP row, its registration, and its dispatch arm and body.

Reverting this commit restores the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…sured, one read

An outside static audit of main at b432ed2 claimed three defects in the PCI
claim substrate and netstack, each argued from reading and run by nobody. Each
was traced in the tree here and, where a test could reach it, run.

- A claim's BAR asked for again after its handle closed panics the kernel:
  measured, `bar_map_again` exits 1 on `a handle to a retired SharedMem`.
- netstack keeps the socket of a connection whose client sent no close:
  measured, `netstack_socket_churn` reads 0 streams before four such
  connections and 4 after, and exits 0 with the close request sent.
- A PCI call in flight names its function by a slot the next claim reuses:
  traced and not run. No code on the path stops it, and the test needs the
  kernel to hold a call between two of its own statements, which is an
  actuator the tree does not have and this change, which fixes nothing, does
  not add.

Each measured defect's test is red, so it was deleted by the commit its issue
names.

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

Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

The control for netstack_socket_churn, applied on 06bbc236d, run (exit 0) and reversed in one script:

--- a/tests/toyos-rust-tests/src/bin/netstack_socket_churn.rs
+++ b/tests/toyos-rust-tests/src/bin/netstack_socket_churn.rs
@@ -41,6 +41,7 @@
         let mut byte = [0u8; 1];
         assert_eq!(conn.rx.read(&mut byte), Ok(0), "connection {round}: the host ends it without a byte");
         // Both pipe ends, and no close request.
+        toyos::net::tcp_close(conn.socket_id).expect("the control: the close request this test leaves out");
         drop(conn);
         let asked = Instant::now();
         // Each question is a round trip netstack answers on a pass of its own.

@Japabu

Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Review, round 1, of 23f36bce9 against .claude/agents/reviewer.md

Net: +123 lines in 3 files, all under issues/; production 0, tests 0. The four test commits cancel: git diff origin/main a86cafa06 is empty.

What was checked

  • The reverts. a86cafa06 reversed is patch-identical to ef41f0ad1; 8dd55d06a's parent is 06bbc236d, so its revert brings back the guest binary, both rows, the arm and body, and tests/netcase's receives. Each revert merges clean alone on the head (git merge-tree --merge-base=<commit> <head> <commit>^, exit 0 for both). main lands branches by merge commit, so both commits will be reachable from it.
  • The logs. cap01-06bbc236d.log: exit status 1, PANIC: panicked at src/object/handle.rs:108:9: a handle to a retired SharedMem (koid 197), backtrace through ops::install from sys_device_bar_map, pid 10 test_rs_bar_map_again. srv01-06bbc236d.log: exit status 1 on held 0 stream(s) before them and holds 4 after. srv01-control-06bbc236d.log: PASS, with the first comment's one-line patch. ci-host.log: started one second after the head's commit time, Host: 77 step(s), all green, EXIT=0.
  • BAR (executed, seen). The tree agrees with the panic: bar_object (kernel/src/pcidev/mod.rs:1501) keeps the object in bound.bars, HandleEntry::drop retires it at zero handles, HandleEntry::new asserts. The second arm, read and marked so, holds: ops::install builds the entry before HandleTable::install can answer TableFull, and the refused entry drops.
  • netstack (executed, seen). bridge_piped ends in piped_connections.swap_remove alone (userland/netstack/src/main.rs:1389); handle_tcp_close removes all three (:794); piped_live counts bridges and pending connects; sockets.tcp counts table entries.
  • Slot (traced). Every step holds: pci_slot (kernel/src/syscall/device.rs:168) and sys_device_reg's RegTarget::PciConfig answer a bare number and drop the claim; with_bound (pcidev/mod.rs:1488) checks faulted and bound and nothing of whose; the process-data lock and BOUND[slot] are separate with nothing across them; release leaves a reset function's slot Free and slot::reserve gives a requester its own residue, else the first free slot. A thread is preempted at the lock release that ends pci_slot (the Ring 0 tick sets need_resched, preempt::enable acts on it).
  • Fields and kind. Three status: open, kind: defect, opened: 2026-10-08; slugs unique; both owner tracks and the cited issue exist; no existing issue records any of the three. defect for the unrun one is right by issues/README.md: what a finding is promoted to is "something real that someone should act on — a fix, a measurement, an instrument", and "when unsure, promote"; main already files netstack-removes-a-closed-stream-before-its-fin-leaves.md so. The change that makes the two branches agree is in Three audited kernel races recorded, traced and unexecuted: an IPC rollback's expect, chdir against open, an shm map against the last close #754. No machine is named beyond what the tree already names; the BAR issue's "ask, close, ask" says no more than its title.

NOTE

  • issues/a-claims-bar-asked-for-again-after-its-handle-closed-panics-the-kernel.md:36 and the PR body — the job's line BAR 4 asked for and its handle closed; asking again is credited to the run at 06bbc236d; that run's log has an empty "the job said:", and the line is in cap01-run1.log, a run made before the commit existed — drop the clause or quote the run that shows it.

LAND

@Japabu
Japabu marked this pull request as ready for review October 8, 2026 08:14
@Japabu
Japabu enabled auto-merge October 8, 2026 08:14
@Japabu
Japabu added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit 2e781a4 Oct 8, 2026
6 checks passed
@Japabu
Japabu deleted the wt/toyos-audit-devices branch October 8, 2026 08:54
Japabu added a commit that referenced this pull request Oct 8, 2026
Git merged every file without a conflict. #750 moves
`userland/acpiserver/aml`'s sources and tests; #753 and #755 move issues
alone. No manifest, no lock, no workflow and nothing under `src/` moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
github-merge-queue Bot pushed a commit that referenced this pull request Oct 8, 2026
…nd the SDK resolve together (#746)

Stage 3 of `issues/the-tree-says-who-uses-each-thing.md`. The root,
`kernel/`, `bootloader/`, `userland/` and `toyos/` were five Cargo
resolutions; they are one workspace with one `Cargo.lock`, one
`[profile.toyos]`, one `[patch]` table and one tracked
`.cargo/config.toml`. No directory moves.

Head `dd12c0b32`, on `origin/main` `6f87cdb9c` (#749; none of #757, #759
or #762 had landed when it was merged and measured). It is `9ef866436`,
where the CI readings of the fold itself were taken, plus two merges of
`main` and the close of the stage's issue. Everything owed at the merged
head is in the next section, measured at `dd12c0b32`.

## The merge of #749, measured at `dd12c0b32`

#749 wrote its new dependency edges into `kernel/Cargo.lock` and
`userland/Cargo.lock`, which this branch deletes. Both modify/delete
conflicts are resolved by deleting the file and re-resolving the root
lock. Git merged the root `Cargo.lock` without a conflict into a lock
that is wrong, as the review found: `cargo metadata --locked` on it
exits 101 (`cannot update the lock file … because --locked was passed`).
It had `toyos-userbound`'s edges to `toyos-abi` and `toyos-bootmap`,
which #749 also wrote into the root lock, and not `acpiserver`'s to
`toyos-acpi` and `toyos-aml`, which #749 wrote into userland's alone.
`cargo metadata --offline` re-resolved it. `diff` of git's merged lock
against the re-resolved one is those two lines under `acpiserver` and
nothing else: no package added, no version moved.

| Owed | Command | Result |
|---|---|---|
| The lock resolves as committed | `cargo metadata --locked
--format-version 1` | exit 0 at this head by the host suite's step "the
licences of what ships", which runs `cargo metadata --locked` for every
shipped crate's manifest and is green in `host.log` and in run
37757675374; the hand run's empty stderr (`metadata-locked.err`)
predates the merge commit and recorded no exit |
| The lock's (name, version) pairs are the union of `main`'s five |
`pairs.sh <worktree> 6f87cdb`: the pairs of `Cargo.lock`, `kernel/`,
`bootloader/`, `userland/` and `toyos/Cargo.lock` at `6f87cdb9c`, `sort
-u`, against the root lock's | 692 against 692, `diff` exit 0
(`pairs.out`) |
| The folded kernel and loader are the control's bytes | `prove.sh
6f87cdb dd12c0b …`, the round 3 script unchanged | exit 0; all four
`control vs fold` byte rows `cmp` exit 0; every row in "The checks"
below (`prove.out`) |
| No new reader of a compiled-in path | `git diff -U0 e3bdff8
dd12c0b -- tests src toyos-blackbox toyos-symbols userland/symbolize`,
its added lines searched for `\.rs`, `taken at`, `panicked at`,
`Location`, `file()`, `PREVIOUS_PANIC`, `strip_prefix`, `src/`, `pure/`
| the merge touches five files there, all under `tests/`; 13 hits, of
which 4 are diff headers and 9 the field `info.rsdp`; none reads a path.
`tests/common/power.rs:429` is still the one reader outside fixtures,
and reads `taken at kernel/src/hardlockup/probe.rs`
(`readers-merge.diff`, `readers-hits.txt`) |
| `cargo run -- --ci host`, once, on the development machine | at
`dd12c0b32`, `cargo run -- --ci host > host.log 2>&1; echo EXIT=$?` |
exit 0; the log ends `[ci] Host: 77 step(s), all green`; 1-minute load
26.60 when it started (`host.log`) |

The logs are in the round's scratch directory (`orch/oneworkspace-r4/`),
which a reader of this pull request cannot reach; `prove.out` and
`pairs.sh` are in the round 4 comment.

## What changed, per decision

- **Members.** `kernel`, `bootloader`, `toyos` and userland's 40
packages join the root `[workspace]`. `userland/Cargo.toml`, four locks,
three `rust-toolchain.toml` and three per-directory `.cargo/config.toml`
are deleted. The toolchain files chose nothing the build read: every
guest `cargo` already runs under `RUSTUP_TOOLCHAIN` naming its sysroot.
- **Flags.** `build.target` is gone, since every guest build already
passes `--target`. The root config holds one `[target.<triple>]` table
per guest triple, six, each with the flags its directory's config gave
it. A host build takes none, as before. Two things do change:
- The guest crates outside the workspace that are built from their own
directory for a ToyOS triple (`tests/toyos-rust-tests` and its `tls-*`
crates) now take `-Dwarnings`, because cargo reads the tracked root
config from above them. On a checkout without a local config they took
no flags.
- The one build that sets `RUSTFLAGS` itself (the test binaries linked
against a `cdylib`, `src/build.rs`) takes none of the table: the
variable replaces it.
- **Profiles.** The root's `[profile.dev]` is `opt-level = 2`. The
kernel library's host tests, its model controls, the SDK's tests and
every surveyed userland crate's host tests used to resolve in their own
workspaces and ran at `opt-level = 0`; they now run at 2.
- **What stays apart** (the root manifest's `exclude` says why at each
entry): `rust/`; `tests/toyos-rust-tests` and its `tls-*` crates and
`tests/ssh-client-host`, because `[patch]` is workspace-wide and they
patch or refuse what the root patches; and `userland/libc`. libc keeps
its own lock because that lock is an input of the sysroot key: as a
member it would be resolved by the root lock, and every dependency
change of any member would move the key and rebuild every sysroot. The
price is a sixth resolution of `toyos`, `toyos-abi`, `toyos-elf`,
`toyos-osrelease` and `dlmalloc` that nothing holds to the root's (both
carry `dlmalloc` 0.2.13 today).
- **The lock** is every `[[package]]` of the five locks, deduplicated
and resolved by `cargo metadata`. Nothing was `cargo update`d. Since the
lock reviewed at `88bcbf4d3` it has changed by #749's four edges alone
(see the section above); `cargo metadata --locked` exits 0 at this head.
- **One target directory.** Every guest is built at the root with `-p`
into `target/`. A stale sysroot used to `cargo clean` a crate's own
target; that would now empty the build system's own, so `Stale::All`
removes `target/toyos` and the guest triples' directories instead, and
the `cargo clean` path, its member assertion and its test are deleted.
- **Kernel and loader build one after the other.** They were built on
two threads. In one target directory cargo serialises them anyway
(measured in round 1: the second prints `Blocking waiting for file lock
on artifact directory`), so the thread scope is deleted.
- **The host suite** can no longer be `--workspace`: the kernel binary,
the loader and most of userland do not build for a host. `src/hostws.rs`
says which members a host tests, and the workspace test and clippy runs
`--exclude` the rest by package name.
- **Fork clones.** The tracked config includes the gitignored
`.cargo/local.toml` when it exists, and `implementer.md` names it.
- **The merge of #745.** `src/sysroot.rs` keeps both sides:
`SYSROOT_SOURCES` carries `"sdk/std"` and `SYSROOT_MANIFESTS` ends in
`".cargo/config.toml"`; its test keeps both loops;
`issues/toyos-has-its-own-allocator.md` keeps `sdk/std/sys/alloc.rs` and
"from the kernel's graph in `Cargo.lock`". Git merged all three without
a conflict.
- **The host's own apps build where the userland tests build.**
`src/ci.rs`'s apps step passes `--target` only where it checks another
host's triple. On `main` the `userland/*` test steps and the host's apps
step both named the host triple and shared `userland/target/<host
triple>`. The fold took `--target` off the test steps, which no longer
need it to keep a guest triple out, and left it on the apps step: the
tests filled `target/debug`, the apps `target/<host triple>`, and every
dependency was compiled twice. That is what made the sealed tree larger
than `main`'s (see CI).
- **The merges of `main`.** #747, #748, #750, #751, #753 and #755 merged
without a conflict. #749 did not: see the section above.
- **The merge of #752.** Git merged it without a conflict: both
workflows' `host` jobs keep `CARGO_PROFILE_DEV_DEBUG: line-tables-only`
in `env:`, and `carry()` no longer sets it. No manifest and no lock
moved in the merge.
- **Issues.**
`issues/the-tree-resolves-in-five-cargo-locks-not-one.md` is deleted:
the one thing it named as left, the T14's run of the metal profile on
the folded build, ran green at `db55db96a`, and review round 2 ruled no
boot owed for what followed on two conditions, both in the section
above. Stages 2 and 3 of `issues/the-tree-says-who-uses-each-thing.md`
now read "Landed in #724, #732 and #738" and "Landed in #746". What the
file carried that stays true is the root manifest's `exclude`, which
says why each excluded directory keeps its own resolution; the deleting
commit's message carries the rest (libc's second resolution of five
crates, and `miniz_oxide` 0.8.9 beside 0.9.1 until `png` takes 0.9).
`issues/cargo-run-inside-kernel-loom-or-kernel-sim-builds-for-a-bare-target.md`
is closed on the two in-directory runs at `9ef866436`.
`issues/the-sdk-is-linted-by-no-clippy-run.md` is filed and names its
owner, the build system.

## The fold changed the kernel's source paths, and the proof did not see
it

The T14's run of the whole metal profile at `88bcbf4d3` exited 1: 295
passed, 1 failed, 30 boots. The red row was
`hard_lockup_ends_a_deaf_cpu`. Its judge looked for `taken at
src/hardlockup/probe.rs` in the previous boot's panic record, and the
readback's loader log says `taken at
kernel/src/hardlockup/probe.rs:145:29`.

**What changed in the kernel's strings.** Cargo hands rustc a workspace
member's source by its path from the workspace root, and rustc writes
that path into every panic and `Location`. The kernel's root was
`kernel/`; it is now the repository. So `src/...` became
`kernel/src/...`, and a path dependency outside the old root, which the
base named by the checkout's absolute path, is now named from the
repository root (`toyos-abi/src/...`). Read from the actuator kernel
staged at this head: 254 distinct `.rs` paths, 158 under `kernel/`, 38
under a `toyos-*` crate or `bcachefs`, none bare `src/` or `pure/`, none
naming the worktree.

**Why the proof did not see it.** Both of its oracles were blind to it
by construction:
- The byte row compared the fold against a control that is the base with
its workspace root moved up. The control moved the root too, so it
carries the same new paths and the bytes agree.
- The `rustc`-lines row compared base against fold after a `sed` that
rewrites `(kernel/|bootloader/)?(src|pure)/x.rs` and
`ROOT/<crate>/src/lib.rs` to one form. That rewrite is needed, or every
path crate's line differs and the row can show nothing else; but it
absorbed the change without reporting it.

`prove.sh` now reports what that rewrite absorbs: per artifact, how many
crates' source arguments were renamed, and a diff of the `.rs` paths the
artifact carries, base against fold and control against fold. The script
is in the round 3 comment and has run twice since, at `9ef866436` and at
this head.

**Every reader of a compiled-in path.** I searched the harness, the
guest tests, the build system, `toyos-blackbox`, `toyos-symbols`,
`userland/symbolize`, the loader and the kernel's panic path for path
literals, prefix strips and `Location` readers. One reader matches a
compiled-in path by its prefix: `tests/common/power.rs`, the red row's
judge, now fixed to the path the kernel records. Everything else is
prefix-blind (`panicked at`, a file name with its line) or a synthetic
fixture. The kernel's panic slot keeps the last 96 bytes of a path; the
longest kernel path is 46, so nothing is cut. Userland's panic sites
gain a `userland/` prefix the same way; no test reads one.

**The record rows.** The judging asked to record three
`boot.testcases-bounds.*` rows. They are not this change's: that boot
was already staged on the base and unrecorded, and `main` recorded it in
#745. They arrive with the merge and nothing is committed here.

## What the fold changes in what is built

The lock row was measured at `dd12c0b32` against `6f87cdb9c`, the
`rustc` row by `prove.sh` at the same pair; the two `cargo tree` rows at
`88bcbf4d3`, and were not taken again.

| Measured | Result |
|---|---|
| Lock: (name, version) pairs, the fold's against the union of
`origin/main`'s five | identical, 692 pairs, `diff` exit 0 |
| Lock: sources | registry `getrandom` 0.2.17, 0.3.4, 0.4.2 are gone;
the forks at the same versions remain |
| Kernel and loader, both arches: every `rustc` command line of `cargo
build -v`, base against fold, path and cargo's path-derived hashes taken
out | identical, `diff` exit 0: 31 units per kernel, 55 and 37 per
loader |
| Userland, both triples: `cargo tree -e features` over every program,
base against fold | identical, `cmp` exit 0 |
| Host members: the same | `diff` exit 1, on `getrandom`'s source alone
|

So one resolved crate changes: the build system and the other host
members compile the ToyOS forks of `getrandom` 0.2.17, 0.3.4 and 0.4.2
instead of the registry's, same versions, same features. And every
source path compiled into the kernel and the loader changes, as above.

## The checks (high-risk: build system)

Measured at `dd12c0b32` against `origin/main` `6f87cdb9c` by `prove.sh`
(the script of the round 3 comment, unchanged), exit 0; its output is in
the round 4 comment. Round 3 measured the same rows at `9ef866436`
against `b432ed21c`, round 1 at `88bcbf4d3` against `e7010129f`.

**Negative control.** The whole change reverted is the base. A second
control is the base with only its workspace root moved up, keeping the
crate's own base lock and its profile. It is given the fold's root
`.cargo/config.toml`, so the control does not hold the flags: the one
row that does is base against fold on normalised `rustc` lines.

**Oracle.** Bytes and cargo's own command lines. Each cell is its own
`cmp` or `diff` exit:

| | kernel x86_64 | kernel AArch64 | loader x86_64 | loader AArch64 |
|---|---|---|---|---|
| control vs fold, bytes | 0 | 0 | 0 | 0 |
| fold vs fold rebuilt, bytes | 0 | 0 | 0 | 0 |
| base vs fold, bytes | 1 | 1 | 1 | 1 |
| base vs fold, `rustc` lines normalised | 0 | 0 | 0 | 0 |
| control vs fold, `rustc` lines verbatim | 0 | 0 | 0 | 0 |

What the normalisation absorbs, reported by the three `paths` rows: base
against fold, cargo hands rustc another source path for 29 of 31 crates
of each kernel and for 35 of 50 and 13 of 35 crates of the loaders
(`diff` exit 1 each, as expected); the `.rs` paths the x86-64 kernel
carries are 250 on both sides, of which the base has 39 under the tree's
absolute path and 150 from the crate's own root and the fold none of
either (`diff` exit 1); control against fold the artifacts' paths are
identical (`diff` exit 0, all four).

**Mutations**, each
on a fresh copy of the fold: M1 (drop the `[target.x86_64-unknown-uefi]`
table) loader build exit 101; M2 (lock `dlmalloc` at 0.2.12) `cmp` exit
1 and lines `diff` exit 1; M3 (select `bcachefs` beside the kernel in
one `cargo`) kernel build exit 101.

## Gates

The rows of the section "The merge of #749" were read at `dd12c0b32`.
Every row below was read at `9ef866436` unless it says otherwise, each
once, the narrowest that judges it. `ci.yml` runs on the push of
`dd12c0b32`; its result is not in this body.

| Gate | Result |
|---|---|
| `cargo run -- --ci host` | at `dd12c0b32`, development machine: exit
0, `[ci] Host: 77 step(s), all green`. Linux runner at `9ef866436`:
`ci.yml` run 37740454881 `host` success; cold inside `--ci seal`,
nightly run 37740449787: `[ci] Seal: 80 step(s), all green`; on macOS,
the same nightly's `portability-macos`: success |
| `cargo test --lib ci::tests` (the changed step's own test) | exit 0,
13 passed |
| The images and the guest suite | at `9ef866436`, run 37740454881:
`toolchain / build` and `guest / suite` success (KVM); run 37740449787:
`toolchain / build` and `tcg / suite` success. At `e3bdff8af`, run
37755369755: `host` and `toolchain / build` success, `guest / suite`
still running when read. Not run locally, and not read at `dd12c0b32` |
| `prove.sh 6f87cdb dd12c0b …` | exit 0; every row as in the table
above |
| `cargo test` inside `kernel/loom` | exit 0 |
| `cargo test` inside `kernel/sim` | exit 0 |
| Cold wall clock, x86-64 kernel and loader (`wall.sh`, one run) | base,
two cargos side by side: 24 s, 1-minute load 34.92 before it. Fold, one
after the other: 20 s, load 42.23. Other agents' builds were running, so
the two are not a controlled pair; the fold was not slower |
| Metal profile | every row green at `db55db96a` (comment 6048782042).
Since then the branch changed `src/ci.rs` and the root lock's two
`acpiserver` edges; the kernel sources that moved are `main`'s own
landings (#747, #748, #749), merged in. Review round 2, ruling (3), owes
no boot for the merge of #749 on two conditions, both met above |
| `cargo test --manifest-path userland/acpiserver/aml/Cargo.toml` at
`e3bdff8af` | exit 0 |
| `git status --porcelain --ignore-submodules=none` at `dd12c0b32` |
empty |

`issues/cargo-run-inside-kernel-loom-or-kernel-sim-builds-for-a-bare-target.md`'s
close now stands on the two in-directory runs at `9ef866436`.

The logs of these rows are files in the scratch directory of the round
that took them (`orch/oneworkspace-r3/`), which a reader of this pull
request cannot reach; the proof's and the measurements' outputs are in
the round 3 comment.

## CI

**Why the sealed tree was larger than `main`'s, measured.** A
`workflow_dispatch` of `nightly.yml` at `88bcbf4d3` (run 37685714260)
sealed `9348536345 B in 20064 files`, red. Units compiled per step,
counted from that log and from `main`'s nightly at `b432ed21c` (run
37717000719, sealed `18708 files, 7664839895 B`):

| step | `88bcbf4d3` | `main` |
|---|---|---|
| the driver's own build | 203 | 203 |
| the build system | 207 | 207 |
| the workspace's host members | 99 | 99 |
| clippy, warnings denied | 412 | 417 |
| the controls | 56 | 56 |
| `userland/*` | 225 | 289 |
| the apps for linux | 282 | 47 |
| the apps for macos | 248 | 248 |
| the apps for windows | 239 | 239 |

Of the 256 distinct crates the apps-for-linux step compiled at
`88bcbf4d3`, 226 had been compiled by an earlier step of the same run;
30 by none. The cause is the target directory: the test steps built
without `--target` into `target/debug`, the apps step with `--target
x86_64-unknown-linux-gnu` into `target/x86_64-unknown-linux-gnu`.
Profile, features and `RUSTFLAGS` are the same in both.

**The fix, measured once on the development machine** (`share.sh` in the
round 3 comment; cold, a target of its own, `aarch64-apple-darwin`):
after the fourteen test steps' builds (271 units), the ten apps with
`--target <host>` compile 270 units and add 616,616 KiB under
`target/<host>` and 135,064 KiB under `target/debug`; the same ten
without `--target` then compile 47 units and add 107,856 KiB. The 47 are
the same crates `main`'s step compiles on the runner.

**The seal at `9ef866436`, nightly run 37740449787** (conclusion
success: `host`, `portability-linux`, `portability-macos`, `toolchain /
build`, `tcg / suite`):

```
the cache entry, read by content: none restored: the run is cold
the apps for linux: 10 app(s) pass `cargo build`; …
the tree, sealed as the host cache's entry: 17010 files, 7493968284 B of the 8000000000 B an entry may hold; sealed: 2496 sources, built on Linux X64 ubuntu24 20261004.327.1, every target dated as built
[ci] Seal: 80 step(s), all green
```

Units per step in its log, against the two columns above:

| step | `9ef866436` | `88bcbf4d3` | `main` |
|---|---|---|---|
| `userland/*` | 225 | 225 | 289 |
| the apps for linux | 42 | 282 | 47 |
| the apps for macos | 264 | 248 | 248 |
| the apps for windows | 240 | 239 | 239 |

Every other step compiles what it did at `88bcbf4d3`. I expected 47 for
the Linux apps: it is `main`'s 47 less `crc32fast`, `log`, `memchr`,
`smallvec` and `toyos-keymap`, which an earlier step had compiled. I did
not expect the macOS step's 16 more: all are host-side units (`syn`,
`thiserror-impl`, `tokio-macros`, `futures-macro`, `autocfg` and the
like), which the Linux step's `--target` build used to compile for the
host and which the first `--target` step now compiles instead. The four
steps together compile 771 units against 994 at `88bcbf4d3` and 823 on
`main`.

- Margin: 506,031,716 B under the limit, 6.3 %, on image 20261004.327.1.
`main`'s 7,664,839,895 B was sealed on 20260927.320.1. The one pair of
figures there is for the two images is `f260e0b98`, built with full
debuginfo before #752 cut it to line tables: 8,540,783,725 B on
20260927.320.1 (run 37292450697) against 8,182,940,473 B on
20261004.327.1 (run 37601225884), 357,843,252 B or 4.2 % less on the
newer. So this head's figure and `main`'s are not a pair, and this head
is unmeasured on the older image.
- Against the same branch before the fix and before #752: 9,348,536,345
B at `88bcbf4d3` on 20260927.320.1.

**`ci.yml` run 37740454881 at `9ef866436`:** `host`, `toolchain / build`
and `guest / suite` success.

After this lands every `host` check runs cold until the first nightly on
`main` seals and saves: the path list is the cache's version.

**Toolchain keys.** The fold moves the sysroot key once:
`userland/.cargo/config.toml` was one of its inputs and
`.cargo/config.toml` replaces it. From now on a change to any guest
triple's flags moves that key. The merges of #747 and #749 moved
`toyos-abi` and `toyos`, so the sysroot key moved with `main`; the key
at this head was not read here.

## No new gate, test or dependency

No guest test is added or changed. No dependency is added. The proof is
a one-off script because its subject is this one change against its
base.

## Size

`git diff --shortstat origin/main...HEAD` at `dd12c0b32`: 53 files,
+5454 −7765. Without the locks: 48 files, +446 −704. `src/`,
`tests/toyos.rs` and `tests/common/`: 12 files, +237 −360, of which
tests are roughly +65 −115 by my reading of the hunks (an estimate, not
a count). `issues/`: 16 files, +49 −115.

## What I am unsure of

- **The seal on the older runner image.** GitHub serves two; this branch
was sealed on the newer one, at `9ef866436`. The only pair of figures
for the two is `f260e0b98` with full debuginfo (above): the older image
sealed it 357,843,252 B larger. Carried unscaled onto this tree that
leaves 148,188,464 B under the limit on the older image; scaled by the
pair's ratio, about 178 MB. Both are arithmetic, not a run.
- **`dd12c0b32` itself:** `ci.yml` run 37757675374 has `host` and
`toolchain / build` success at it; its `guest / suite` is what the
landing waits on. #757 (`b6bcb9691`) landed on `main` after this head
was merged and measured: ten source files under `kernel/src`,
`toyos-abi/src`, `toyos-userbound/src` and `tests/toyos-rust-tests`, no
manifest and no lock; `git merge-tree --write-tree dd12c0b b6bcb96`
exits 0. Nothing here was measured with it in. It differs from the
sealed head by `main`'s #749, #750, #753 and #755 and the root lock's
two edges; the seal's byte count at this head is unmeasured.
- **Build wall clock.** One run under load; the fold was not slower.
- **Clippy reaches further.** The bare-target shapes now also lint the
kernel's and the loader's path dependencies for those targets.
- **The licence gate reads a superset.** `--all-features` for the kernel
now turns on every member's features. It judges more than ships.
- **The runner's cargo.** The nightly's `host` at `88bcbf4d3` parsed the
optional `include`, so the runner's cargo accepts it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
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