Skip to content

chdir ends its VFS guard before taking process-data, the order open takes - #759

Merged
Japabu merged 3 commits into
mainfrom
wt/toyos-chdirlock
Oct 8, 2026
Merged

Japabu merged 3 commits into
mainfrom
wt/toyos-chdirlock

Conversation

@Japabu

@Japabu Japabu commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

sys_chdir held the VFS lock into the process-data lock, the inverse of sys_open's order, so two threads of one process could deadlock the kernel. Traced by the kernel audit (pull request #754), never executed. Its issue file is carried here by 7a1ad760f and deleted by 96a25fe4b.

The defect

sys_chdir (kernel/src/syscall/fs.rs) was match vfs::lock().cd(&cwd, path) { Ok(new_cwd) => process::with_process_data(..), .. }. The guard is a scrutinee temporary, which in edition 2021 lives to the end of the match, so the Ok arm took process-data with the VFS lock held: VFS then process-data. sys_open takes process-data and ops::open takes vfs::lock() inside it: process-data then VFS. Process data is one Arc per process, shared by its threads; the VFS lock is global. A chdir and an open of one process on two CPUs could each hold what the other waited for, and the ticket lock's spin ceiling panics the kernel.

The fix

let resolved = vfs::lock().cd(&cwd, path); and then the original match resolved: the guard ends with the let. It is the form sys_open and the loader (loader/mod.rs, both vfs::lock() lets) already use. Kernel: +3 −1, one line of it the invariant at the site.

A ranked lock type that would refuse the order was not built: the kernel has no lock-order type, and one over its six lock kinds is a design of its own. issues/a-user-copy-demand-pages-under-whatever-its-caller-holds.md, whose exit is "a type or a checked lock level", now names the VFS lock beside the three it already held.

The closed issue's exit, and the clause this branch drops

The exit had two clauses. The first, that chdir does not hold the VFS guard while it takes process-data, is met by the fix and shown by the oracle below. The second asked for a test that stages chdir past a successful lookup with its guard alive and drives open into it. That test is not added. A guest test that races the two syscalls passes on the broken kernel almost every run, so it cannot fail; staging the window needs an actuator that holds sys_chdir inside its Ok arm, which is kernel code shipped under a feature to guard three lines whose correctness is the language's temporary-lifetime rule. No host model compiles the two syscalls.

High-risk checks (a syscall, lock order)

Independent oracle and negative control in one: clippy's significant_drop_in_scrutinee, a third-party checker of the temporary-lifetime rule the defect is. It is silent in the gate because no kernel guard carries #[clippy::has_significant_drop]; for the measurement sync::LockGuard was marked by an uncommitted one-line patch, and the red arm is the whole kernel change reversed on the same tree. Run by one script that applies and reverses both patches as checked patches and leaves the tree clean. Patches and both logs, whole: #759 (comment).

Command, in kernel/: cargo clippy --target x86_64-unknown-none -- -A clippy::all -D clippy::significant_drop_in_scrutinee.

arm exit reports
sys_chdir as origin/main has it 101 6: arch/x86_64/idt/mod.rs:470, loader/mod.rs:748, user_ptr.rs:42, syscall/fs.rs:44, syscall/fs.rs:86, syscall/vm.rs:233
sys_chdir at this head 101 5: the same without syscall/fs.rs:86

syscall/fs.rs:86 is sys_chdir's match, and it is the only line that differs. Neither arm is clean, and the lint is not a gate: the five sites left are in both logs and are not this defect. Each, by reading:

  • arch/x86_64/idt/mod.rs:470: a for over IDT.lock().entries at boot; its body writes entries and takes no lock.
  • loader/mod.rs:748: the /system/lib fallback's match vfs::lock().open_backing_identified(..); the VFS guard is held across a log! on the error arm, and no arm takes process-data.
  • user_ptr.rs:42: translate_user's if let over the address-space lock; the arm returns.
  • syscall/fs.rs:44: sys_readdir's match vfs::lock().list(..); both arms move or return.
  • syscall/vm.rs:233: sys_dlopen's match vfs::lock().open_backing_identified(..); the VFS guard is held across a log! on the error arm.

So on main the lint is 1 defect in 6 reports, on the x86-64 kernel with default features and LockGuard alone marked. That, what it does not see (an if let Some(x) = L.lock().as_mut() whose value borrows from the guard, and any inversion through a named guard) and what adopting it would cost are one row in issues/clippy-stage-two-is-lints-one-at-a-time.md. No lint and no lock type is built here.

The first round's logs said the same 6 and 5 for the block form of the fix; this round's are of the head's form.

Gates, at 54e8a0791548f31c0a95d5ba64a1c07a1dd4b233

  • cargo run -- --ci host: EXIT=0, "Host: 77 step(s), all green". The log is 484,698 bytes; its step lines and exit are in chdir ends its VFS guard before taking process-data, the order open takes #759 (comment).
  • A test that executes sys_chdir: run on the T14, green. tests/toyos-rust-tests/src/bin/abuse_cwd_growth.rs drives both arms (refused and accepted chdirs, with getcwd read back). It is a discovered binary, and discovered binaries ride only the T14's shared boot: no QEMU boot runs it. cargo test --test toyos-build -- --metal --metal-readback <scratch>/metal abuse_cwd_growth built the shared image from the clean tree at this head with test_rs_abuse_cwd_growth as its one job (sha256 449cd163…93dc4ea7). The orchestrator booted that image on the T14 at 54e8a0791: boot exit 0, judge exit 0, [metal] 1 passed, 0 failed, 1 boot(s), ===TEST_END test_rs_abuse_cwd_growth exit=0=== (chdir ends its VFS guard before taking process-data, the order open takes #759 (comment)). The staging log is in the evidence comment.
  • No QEMU guest test was run this round: none executes sys_chdir. The first round's machine_shutdown run reached spawn_cwd, which this diff does not touch, and is withdrawn as evidence.

Unsure of

  • The five residual sites were read, not measured, and only for the VFS/process-data pair and for a second lock under the guard.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A

Japabu and others added 2 commits October 8, 2026 10:13
Carried from the kernel audit's branch (pull request 754), which traced it
without executing it, so the record lands with the commit that closes it.

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

`sys_chdir` matched on `vfs::lock().cd(...)`, whose guard — a scrutinee
temporary — lived to the end of the `match`, so the `Ok` arm took the
process-data lock while the VFS lock was still held: order VFS then
process-data. `sys_open` takes process-data first and `ops::open` takes VFS
inside it: order process-data then VFS. Two threads of one process (process
data is a per-process Arc shared by its threads; VFS is global) running chdir
and open on two CPUs could each hold the lock the other waited for; the ticket
spinlock does not break the cycle and its spin ceiling panics the kernel.

chdir now resolves under the VFS guard in a scope, drops it, then takes
process-data alone — the order open takes. The guard is a named local, not a
scrutinee temporary, so it is dropped where the block closes rather than at the
end of the match.

Closes the carried issue; deletes its file.

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

Measurement scaffold for the independent oracle above — applied for the RED/GREEN runs, then removed (not committed). It tells clippy the ticket-lock guard is significant-drop, which the shipping tree deliberately does not, so the significant_drop_in_scrutinee lint can see which guards outlive their match:

--- a/kernel/src/sync.rs
+++ b/kernel/src/sync.rs
@@ pub struct LockGuard
+#[clippy::has_significant_drop]
 pub struct LockGuard<'a, T> {

With it applied, toggling only the sys_chdir body between origin/main and this branch:

  • origin/main chdir: cargo clippy --target x86_64-unknown-none -- -A clippy::all -W clippy::significant_drop_in_scrutinee reports src/syscall/fs.rs:44 and src/syscall/fs.rs:86.
  • this branch's chdir: reports src/syscall/fs.rs:44 only.

fs.rs:44 is sys_readdir, which holds the VFS guard across its match but takes no second lock inside it — not the inversion, out of this fix's fence.

@Japabu

Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 1 of wt/toyos-chdirlock at 96a25fe4bb9d3300bf62b1248d578d8621ef598f against origin/main 0174516240c1e7e7b8b692d30b4093a485a36757. Read only: I ran no build, no lint and no test; every lint count below is read from the implementer's own logs.

Net: 1 file, +11 −6, all production (kernel/src/syscall/fs.rs); tests 0. The issue file is added by 7a1ad760f and deleted by 96a25fe4b, so it nets to nothing.

BLOCKER

  • PR body, "Gates" — cargo run -- --ci host is absent — the body carries a hand-typed cargo clippy --target x86_64-unknown-none -- -D warnings instead, which is one architecture, no $ADOPTED lints and none of src/clippy.rs's feature shapes; the host gate's command, exit code and log at this head are owed.
  • PR body, "Gates" — no guest test that executes sys_chdir was run — machine_shutdown is justified by "spawns run the VFS cd path", which is spawn_cwd (kernel/src/syscall/fs.rs:102), a function this diff does not touch. tests/toyos-rust-tests/src/bin/abuse_cwd_growth.rs drives both arms the diff rewrote (the Ok store and the Err return of kernel/src/syscall/fs.rs:91-96) and is not in the body. Its command, exit code and log at this head are owed; the two EXIT=0 lines the body does carry have no log beside them either.
  • PR body, "High-risk checks" — the oracle's stated result is contradicted by its own logs — the body says the lint "fires on fs.rs:86 and :44" on origin/main and "only on :44" on the fix. oracle-red.log ends "6 previous errors" and oracle-green.log "5 previous errors": arch/x86_64/idt/mod.rs:470, loader/mod.rs:748, user_ptr.rs:42, syscall/fs.rs:44, syscall/vm.rs:233, plus fs.rs:86 on red. The red/green difference (the one :86 line) does hold, but the body disposes of one residual site of five, and two of the four it leaves out (loader/mod.rs:748, syscall/vm.rs:233) hold the same global VFS guard across their arms. The body must carry both logs as they are and account for every site.

NOTE

  • kernel/src/syscall/fs.rs:89-97 — the fix is larger than the tree's own form of it — let resolved = vfs::lock().cd(&cwd, path); followed by the original match resolved { .. } drops the guard at the end of the let and is +2 −1 against main. That is the form sys_open (fs.rs:37) and loader/mod.rs:374 and :743 already use for the same reason; the block, the named guard and the early return are a second idiom for it.
  • issues/clippy-stage-two-is-lints-one-at-a-time.md, issues/a-user-copy-demand-pages-under-whatever-its-caller-holds.md — what the scaffold measured is recorded nowhere but a PR comment — see the ruling on (1) below; one row in the clippy track with the measured counts, and VFS named beside the three locks whose order the open finding already says nothing checks.
  • PR body — the carried issue's exit names a staged test ("A test that stages chdir past a successful lookup ... completes both without deadlock") that the branch does not add; the body deletes the issue without saying that clause was dropped or why — prose.
  • PR body, "The fix" — "Priority (a) in the brief" cites a text the merge commit's reader does not have — prose.

The four questions

(1) One fixed site, a lint, or a ranked lock. One fixed site is enough to land this fix; neither mechanism is owed in this branch, and the lint is owed a record.

  • A ranked lock type: not here. The kernel has six lock kinds (sync::Lock, Masked, OwedLock, SleepLock, block's Locked, the serial lock) over dozens of statics and per-object Arc<Lock<_>>s; ranking them is a design of its own, and issues/a-user-copy-demand-pages-under-whatever-its-caller-holds.md already carries "a type or a checked lock level" as its exit. That exit is where VFS belongs.
  • The lint: significant_drop_in_scrutinee is silent in the gate only because no guard carries #[clippy::has_significant_drop]. From oracle-green.log (kernel, x86_64-unknown-none, default features, fixed tree) turning it on reports 5 sites. I read each: none takes a second lock that another site takes first. fs.rs:44 and user_ptr.rs:42 have arms that only move or return; idt/mod.rs:470 is boot-time; vm.rs:233 and loader/mod.rs:748 hold the VFS guard across a log! on the error path. So on main it is 1 defect in 6 reports.
  • What it does not see, by the same log: if let Some(x) = L.lock().as_mut() sites whose value borrows from the guard are in the tree and not in the log (file_cache.rs:145, gpu.rs:66/72/110, scheduler.rs:275, inbox/mod.rs:590), and neither is any inversion through a named guard, which is the common shape. It checks one spelling of the defect, not the order.
  • Price: one attribute on LockGuard, one flag, five rewrites of sites that are not defects, and counts nobody has measured for aarch64, the actuator and instrument shapes, and SleepGuard/Locked/the serial guard, which the scaffold did not mark. It cannot go into ADOPTED as that list stands: ADOPTED is spliced into every shape, and std's MutexGuard is already marked, so the host workspace would start reporting too, unmeasured. It would have to be a per-shape -W the way undocumented_unsafe_blocks is (src/clippy.rs:165).
  • That is a measured finding of the kind the clippy track adopts lints on, and a change of its own under that track; it is not this fix's fence. I did not run it: the tree would have to be edited to do so.

(2) What chdir stores after dropping the guard. Nothing new is wrong. cwd is a String, not a reference to a directory, so the old code's "checked and stored under one VFS guard" bought nothing: rmdir or rename could run the instant the old match ended and leave the same stale path. Every history the new code allows (resolve, sibling removes the directory, store) ends in the state of the legal serial order "chdir, then rmdir". sys_open, sys_readdir, sys_dlopen and resolve_for_modify already read cwd, drop process-data and resolve later. The read-cwd-then-store pair was two critical sections before the diff and still is; the diff does not move it.

(3) Other inversions. None found, in what I read. Every vfs::lock() in the kernel: syscall/fs.rs (all sites), object/ops.rs:95, syscall/vm.rs:216/233, loader/mod.rs:374/743/748, revoke_selftest.rs:22/31/44, main.rs:470/475. Only ops::open nests it under process-data; after this diff no holder of the VFS guard takes process-data. sys_readlink and sys_readdir write to user memory under or after the guard, but UserBytesMut is pinned in dispatch before the body runs and write_at cannot fault, so that is not a hidden VFS-then-process-data edge. Beneath the VFS the mounts take FILE_CACHE only, and file_cache.rs never takes the VFS. Every lock in a match/if let/while let/for scrutinee across kernel/src (the five in the log, plus file_cache.rs:145, gpt.rs:127, gpu.rs:66/72/110, drivers/xhci/mod.rs:350/1589/1656, inbox/mod.rs:590, scheduler.rs:275, arch/x86_64/vtd/mod.rs:122, object/device.rs:172, syscall/machine.rs:258, arch/x86_64/idt/exceptions.rs:311, revoke_selftest.rs): none holds its guard into a blocking acquisition that another site orders the other way; machine.rs:258 and exceptions.rs:311 are try_lock. Not covered: pairs of named guards outside the VFS/process-data pair. I did not trace every pair of the kernel's locks, and this review does not claim the kernel is free of them.

(4) A red test. None is owed. A type is the ranked lock of (1). No host model compiles the two syscalls. A guest test that races chdir against open passes on the broken kernel almost every run, which is a test that cannot fail; staging the window needs an actuator that holds sys_chdir inside its Ok arm, kernel code shipped under a feature to guard three lines whose correctness is the language's temporary-lifetime rule. The scaffolded lint, red on the reverted site and green on the fix, is the measurement this change needs, once the body reports it truthfully.

SEND BACK

…e lint's measurement is recorded

`sys_chdir` binds `vfs::lock().cd(..)` to a `let` and matches on the value,
the form `sys_open` and the loader already use: the guard ends with the
statement, and the original `match` stands. The block, the named guard and the
early return were a second idiom for the same thing.

The scaffolded `significant_drop_in_scrutinee` run that served as this fix's
oracle is recorded in the clippy track, with its counts (6 sites on main, 5
with chdir fixed, x86_64 kernel, default features, `LockGuard` alone marked)
and what it does not see. The user-copy finding names the VFS lock beside the
three whose order nothing checks.

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

Evidence for round 1's answer, at 54e8a0791548f31c0a95d5ba64a1c07a1dd4b233. Every log is whole except the host run's, which is 484,698 bytes: its [ci] step lines and exit are below. The one edit to any of them: the worktree's and the scratch directory's paths are written <worktree> and <scratch>.

The oracle's two patches

scaffold.patch, applied for both lint runs and reversed after, never committed:

--- a/kernel/src/sync.rs
+++ b/kernel/src/sync.rs
@@ -202,4 +202,5 @@
     }
 }
 
+#[clippy::has_significant_drop]
 pub struct LockGuard<'a, T> {

fix.patch, the whole kernel change of this branch; reversed for the red run and re-applied after:

--- a/kernel/src/syscall/fs.rs
+++ b/kernel/src/syscall/fs.rs
@@ -83,7 +83,9 @@
 
 pub(super) fn sys_chdir(path: &str) -> u64 {
     let cwd = process::with_process_data(|d| d.cwd.clone());
-    match vfs::lock().cd(&cwd, path) {
+    // The VFS guard ends with this statement: `sys_open` takes VFS under process-data.
+    let resolved = vfs::lock().cd(&cwd, path);
+    match resolved {
         Ok(new_cwd) => {
             process::with_process_data(|d| d.cwd = new_cwd);
             0

oracle-red.log: the scaffold on, sys_chdir as origin/main has it

$ cargo clippy --target x86_64-unknown-none -- -A clippy::all -D clippy::significant_drop_in_scrutinee  # in kernel/, scaffold.patch applied, sys_chdir as origin/main has it (revert rc=0)
    Checking kernel v0.1.0 (<worktree>/kernel)
error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression
   --> src/arch/x86_64/idt/mod.rs:470:18
    |
470 |     for entry in IDT.lock().entries.iter_mut() {
    |                  ^^^^^^^^^^^^^^^^^^
...
475 |     }
    |      - temporary lives until here
    |
    = note: this might lead to deadlocks or other unexpected behavior
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#significant_drop_in_scrutinee
    = note: requested on the command line with `-D clippy::significant-drop-in-scrutinee`
help: try moving the temporary above the match
    |
470 ~     let value = IDT.lock().entries;
471 ~     for entry in value.iter_mut() {
    |

error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
   --> src/loader/mod.rs:748:27
    |
748 |                     match vfs::lock().open_backing_identified(&fallback) {
    |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
754 |                     }
    |                      - temporary lives until here
    |
    = note: this might lead to deadlocks or other unexpected behavior
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#significant_drop_in_scrutinee
help: try moving the temporary above the match
    |
748 ~                     let value = vfs::lock().open_backing_identified(&fallback);
749 ~                     match value {
    |

error: temporary with significant `Drop` in `if let` scrutinee will live until the end of the `if let` expression
  --> src/user_ptr.rs:42:23
   |
42 |     if let Some(dm) = translate_now(&pt.lock(), addr, access) {
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43 |         return Some(dm);
44 |     }
   |      - temporary lives until here
   |
   = note: this might lead to deadlocks or other unexpected behavior
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#significant_drop_in_scrutinee
help: try moving the temporary above the match
   |
42 ~     let value = translate_now(&pt.lock(), addr, access);
43 ~     if let Some(dm) = value {
   |

error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
  --> src/syscall/fs.rs:44:25
   |
44 |     let entries = match vfs::lock().list(&cwd, path) {
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
47 |     };
   |      - temporary lives until here
   |
   = note: this might lead to deadlocks or other unexpected behavior
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#significant_drop_in_scrutinee
help: try moving the temporary above the match
   |
44 ~     let value = vfs::lock().list(&cwd, path);
45 ~     let entries = match value {
   |

error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
  --> src/syscall/fs.rs:86:11
   |
86 |     match vfs::lock().cd(&cwd, path) {
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^
...
92 |     }
   |      - temporary lives until here
   |
   = note: this might lead to deadlocks or other unexpected behavior
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#significant_drop_in_scrutinee
help: try moving the temporary above the match
   |
86 ~     let value = vfs::lock().cd(&cwd, path);
87 ~     match value {
   |

error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
   --> src/syscall/vm.rs:233:31
    |
233 |     let (backing, id) = match vfs::lock().open_backing_identified(&resolved) {
    |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
239 |     };
    |      - temporary lives until here
    |
    = note: this might lead to deadlocks or other unexpected behavior
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#significant_drop_in_scrutinee
help: try moving the temporary above the match
    |
233 ~     let value = vfs::lock().open_backing_identified(&resolved);
234 ~     let (backing, id) = match value {
    |

error: could not compile `kernel` (bin "kernel") due to 6 previous errors
EXIT=101

oracle-green.log: the scaffold on, sys_chdir as this head has it

$ cargo clippy --target x86_64-unknown-none -- -A clippy::all -D clippy::significant_drop_in_scrutinee  # in kernel/, scaffold.patch applied, sys_chdir as this branch has it
    Checking toyos-abi v0.16.0 (<worktree>/toyos-abi)
    Checking cfg-if v1.0.4
    Checking rustc-demangle v0.1.27
    Checking toyos-elide v0.1.0 (<worktree>/toyos-elide)
    Checking toyos-elf v0.1.0 (<worktree>/toyos-elf)
    Checking toyos-wallclock v0.1.0 (<worktree>/toyos-wallclock)
    Checking toyos-xhci v0.1.0 (<worktree>/toyos-xhci)
    Checking toyos-cpuvuln v0.1.0 (<worktree>/toyos-cpuvuln)
    Checking bcachefs v0.1.0 (<worktree>/bcachefs)
    Checking toyos-gicv3 v0.1.0 (<worktree>/kernel/gicv3)
    Checking toyos-ps2 v0.1.0 (<worktree>/kernel/ps2)
    Checking hashbrown v0.16.1
    Checking toyos-quiesce v0.1.0 (<worktree>/toyos-quiesce)
    Checking toyos-userbound v0.1.0 (<worktree>/toyos-userbound)
    Checking dlmalloc v0.2.13
    Checking toyos-hda v0.1.0 (<worktree>/toyos-hda)
    Checking toyos-blockhold v0.1.0 (<worktree>/toyos-blockhold)
    Checking toyos-dma v0.1.0 (<worktree>/kernel/dma)
    Checking toyos-untrusted v0.1.0 (<worktree>/toyos-untrusted)
    Checking toyos-fat32 v0.1.0 (<worktree>/toyos-fat32)
    Checking toyos-tco v0.1.0 (<worktree>/toyos-tco)
    Checking toyos-blackbox v0.1.0 (<worktree>/toyos-blackbox)
    Checking toyos-gpt v0.1.0 (<worktree>/toyos-gpt)
    Checking toyos-tsc v0.1.0 (<worktree>/toyos-tsc)
    Checking toyos-rootimage v0.1.0 (<worktree>/toyos-rootimage)
    Checking toyos-bootmap v0.1.0 (<worktree>/toyos-bootmap)
    Checking toyos-symbols v0.1.0 (<worktree>/toyos-symbols)
    Checking toyos-pci v0.1.0 (<worktree>/kernel/pci)
    Checking toyos-acpi v0.1.0 (<worktree>/toyos-acpi)
    Checking kernel v0.1.0 (<worktree>/kernel)
error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression
   --> src/arch/x86_64/idt/mod.rs:470:18
    |
470 |     for entry in IDT.lock().entries.iter_mut() {
    |                  ^^^^^^^^^^^^^^^^^^
...
475 |     }
    |      - temporary lives until here
    |
    = note: this might lead to deadlocks or other unexpected behavior
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#significant_drop_in_scrutinee
    = note: requested on the command line with `-D clippy::significant-drop-in-scrutinee`
help: try moving the temporary above the match
    |
470 ~     let value = IDT.lock().entries;
471 ~     for entry in value.iter_mut() {
    |

error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
   --> src/loader/mod.rs:748:27
    |
748 |                     match vfs::lock().open_backing_identified(&fallback) {
    |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
754 |                     }
    |                      - temporary lives until here
    |
    = note: this might lead to deadlocks or other unexpected behavior
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#significant_drop_in_scrutinee
help: try moving the temporary above the match
    |
748 ~                     let value = vfs::lock().open_backing_identified(&fallback);
749 ~                     match value {
    |

error: temporary with significant `Drop` in `if let` scrutinee will live until the end of the `if let` expression
  --> src/user_ptr.rs:42:23
   |
42 |     if let Some(dm) = translate_now(&pt.lock(), addr, access) {
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43 |         return Some(dm);
44 |     }
   |      - temporary lives until here
   |
   = note: this might lead to deadlocks or other unexpected behavior
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#significant_drop_in_scrutinee
help: try moving the temporary above the match
   |
42 ~     let value = translate_now(&pt.lock(), addr, access);
43 ~     if let Some(dm) = value {
   |

error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
  --> src/syscall/fs.rs:44:25
   |
44 |     let entries = match vfs::lock().list(&cwd, path) {
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
47 |     };
   |      - temporary lives until here
   |
   = note: this might lead to deadlocks or other unexpected behavior
   = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#significant_drop_in_scrutinee
help: try moving the temporary above the match
   |
44 ~     let value = vfs::lock().list(&cwd, path);
45 ~     let entries = match value {
   |

error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression
   --> src/syscall/vm.rs:233:31
    |
233 |     let (backing, id) = match vfs::lock().open_backing_identified(&resolved) {
    |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
239 |     };
    |      - temporary lives until here
    |
    = note: this might lead to deadlocks or other unexpected behavior
    = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.98.0/index.html#significant_drop_in_scrutinee
help: try moving the temporary above the match
    |
233 ~     let value = vfs::lock().open_backing_identified(&resolved);
234 ~     let (backing, id) = match value {
    |

error: could not compile `kernel` (bin "kernel") due to 5 previous errors
EXIT=101

cargo run -- --ci host: every [ci] line, and the exit

$ cargo run -- --ci host  # at 54e8a0791548f31c0a95d5ba64a1c07a1dd4b233
08:45:58 === [ci] the build system
08:46:33 [ci] the build system: cargo test --lib
08:46:33 === [ci] the harness's own checks
08:46:39 [ci] the harness's own checks: cargo test --test toyos-checks
08:46:39 === [ci] the host workspace
08:50:14 [ci] the host workspace: cargo test --workspace --exclude toyos-build
08:50:14 === [ci] the kernel's library
08:50:16 [ci] the kernel's library: cargo test --manifest-path kernel/Cargo.toml --target-dir target --lib --features sched-check
08:50:16 === [ci] the licences of what ships
08:50:19 [ci] the licences of what ships: 6 exception(s) stand, and nothing else is refused
08:50:19 === [ci] clippy and the bare targets
08:50:19 [ci] clippy and the bare targets: installed
08:50:19 === [ci] clippy, warnings denied
08:51:18 [ci] clippy, warnings denied: clean
08:51:18 === [ci] kernel-loom without loom
08:51:19 [ci] kernel-loom without loom: cargo test -p kernel-loom --no-default-features --test log_zeroed_init --test log_body_words --test log_cursor
08:51:19 === [ci] control `wake-fence-off`
08:51:20 [ci] control `wake-fence-off`: 1 verdict(s) reached
08:51:20 === [ci] control `lock-acquire-off`
08:51:22 [ci] control `lock-acquire-off`: 1 verdict(s) reached
08:51:22 === [ci] control `owed-fence-off`
08:51:23 [ci] control `owed-fence-off`: 1 verdict(s) reached
08:51:23 === [ci] control `seqlock-writer-fence-off`
08:51:25 [ci] control `seqlock-writer-fence-off`: 1 verdict(s) reached
08:51:25 === [ci] control `serial-try-lock-then-some`
08:51:26 [ci] control `serial-try-lock-then-some`: 2 verdict(s) reached
08:51:26 === [ci] control `reap-raise-relaxed`
08:51:28 [ci] control `reap-raise-relaxed`: 1 verdict(s) reached
08:51:28 === [ci] control `shootdown-serve-relaxed`
08:51:29 [ci] control `shootdown-serve-relaxed`: 2 verdict(s) reached
08:51:29 === [ci] control `shootdown-served-relaxed`
08:51:30 [ci] control `shootdown-served-relaxed`: 1 verdict(s) reached
08:51:30 === [ci] control `roster-commit-relaxed`
08:51:31 [ci] control `roster-commit-relaxed`: 1 verdict(s) reached
08:51:31 === [ci] control `smp-ready-split`
08:51:33 [ci] control `smp-ready-split`: 1 verdict(s) reached
08:51:33 === [ci] control `log-commit-release-off`
08:51:34 [ci] control `log-commit-release-off`: 2 verdict(s) reached
08:51:34 === [ci] control `shard-publish-relaxed`
08:51:36 [ci] control `shard-publish-relaxed`: 1 verdict(s) reached
08:51:36 === [ci] control `log-ring-publish-relaxed`
08:51:37 [ci] control `log-ring-publish-relaxed`: 3 verdict(s) reached
08:51:37 === [ci] control `log-ring-tail-relaxed`
08:51:39 [ci] control `log-ring-tail-relaxed`: 3 verdict(s) reached
08:51:39 === [ci] control `log-ring-loads-swapped`
08:51:40 [ci] control `log-ring-loads-swapped`: 1 verdict(s) reached
08:51:40 === [ci] control `post-is-an-answer`
08:51:42 [ci] control `post-is-an-answer`: 3 verdict(s) reached
08:51:42 === [ci] control `poll-fire-load-store`
08:51:43 [ci] control `poll-fire-load-store`: 2 verdict(s) reached
08:51:43 === [ci] control `sleeplock-acquire-off`
08:51:45 [ci] control `sleeplock-acquire-off`: 2 verdict(s) reached
08:51:45 === [ci] control `device-irq-lossy`
08:51:46 [ci] control `device-irq-lossy`: 1 verdict(s) reached
08:51:46 === [ci] control `dump-report-relaxed`
08:51:48 [ci] control `dump-report-relaxed`: 1 verdict(s) reached
08:51:48 === [ci] control `no-preempt-guard`
08:51:49 [ci] control `no-preempt-guard`: 1 verdict(s) reached
08:51:49 === [ci] control `doorbell-kick-relaxed`
08:51:51 [ci] control `doorbell-kick-relaxed`: 1 verdict(s) reached
08:51:51 === [ci] control `push-fence-relaxed`
08:51:52 [ci] control `push-fence-relaxed`: 1 verdict(s) reached
08:51:52 === [ci] control `commit-ignores-notify`
08:51:54 [ci] control `commit-ignores-notify`: 2 verdict(s) reached
08:51:54 === [ci] control `notify-flag-load-only`
08:51:58 [ci] control `notify-flag-load-only`: 1 verdict(s) reached
08:51:58 === [ci] control `gate-fence-off`
08:52:00 [ci] control `gate-fence-off`: 1 verdict(s) reached
08:52:00 === [ci] control `poll-fire-load-store`
08:52:01 [ci] control `poll-fire-load-store`: 3 verdict(s) reached
08:52:01 === [ci] control `fault-posted-before-it-is-set`
08:52:03 [ci] control `fault-posted-before-it-is-set`: 3 verdict(s) reached
08:52:03 === [ci] control `victim-retires-mid-probe`
08:52:05 [ci] control `victim-retires-mid-probe`: 1 verdict(s) reached
08:52:05 === [ci] control `counting-allocator`
08:52:07 [ci] control `counting-allocator`: 2 verdict(s) reached
08:52:07 === [ci] control `mutate-spawn-skips-the-insert-recheck`
08:52:09 [ci] control `mutate-spawn-skips-the-insert-recheck`: 2 verdict(s) reached
08:52:09 === [ci] control `mutate-claim-teardown-always-wins`
08:52:11 [ci] control `mutate-claim-teardown-always-wins`: 1 verdict(s) reached
08:52:11 === [ci] control `mutate-kill-waits-for-its-victims`
08:52:12 [ci] control `mutate-kill-waits-for-its-victims`: 2 verdict(s) reached
08:52:12 === [ci] control `mutate-first-out-tears-down`
08:52:14 [ci] control `mutate-first-out-tears-down`: 1 verdict(s) reached
08:52:14 === [ci] control `mutate-join-collects-in-a-teardown`
08:52:16 [ci] control `mutate-join-collects-in-a-teardown`: 1 verdict(s) reached
08:52:16 === [ci] control `mutate-last-out-leaves-before-its-teardown`
08:52:17 [ci] control `mutate-last-out-leaves-before-its-teardown`: 2 verdict(s) reached
08:52:17 === [ci] control `mutate-place-skips-the-insert-recheck`
08:52:19 [ci] control `mutate-place-skips-the-insert-recheck`: 1 verdict(s) reached
08:52:19 === [ci] control `mutate-refused-spawn-keeps-the-count`
08:52:21 [ci] control `mutate-refused-spawn-keeps-the-count`: 1 verdict(s) reached
08:52:21 === [ci] control `mutate-landed-child-retires-nothing`
08:52:23 [ci] control `mutate-landed-child-retires-nothing`: 2 verdict(s) reached
08:52:23 === [ci] control `mutate-publish-before-the-children`
08:52:24 [ci] control `mutate-publish-before-the-children`: 1 verdict(s) reached
08:52:24 === [ci] control `mutate-walk-in-one-hold`
08:52:26 [ci] control `mutate-walk-in-one-hold`: 1 verdict(s) reached
08:52:26 === [ci] control `mutate-spawner-handle-after-the-landing`
08:52:28 [ci] control `mutate-spawner-handle-after-the-landing`: 2 verdict(s) reached
08:52:28 === [ci] control `mutate-spawner-handle-before-the-childs-own`
08:52:30 [ci] control `mutate-spawner-handle-before-the-childs-own`: 1 verdict(s) reached
08:52:30 === [ci] control `placement-ignores-staleness`
08:52:34 [ci] control `placement-ignores-staleness`: 1 verdict(s) reached
08:52:34 === [ci] control `mutate-session-end-forgets`
08:52:39 [ci] control `mutate-session-end-forgets`: 1 verdict(s) reached
08:52:39 === [ci] control `mutate-abort-keeps-inflight`
08:52:40 [ci] control `mutate-abort-keeps-inflight`: 1 verdict(s) reached
08:52:40 === [ci] control `mutate-no-reissue-after-loss`
08:52:41 [ci] control `mutate-no-reissue-after-loss`: 1 verdict(s) reached
08:52:41 === [ci] control `publish-relaxed`
08:52:42 [ci] control `publish-relaxed`: 1 verdict(s) reached
08:52:42 === [ci] control `no-clamp`
08:52:43 [ci] control `no-clamp`: 1 verdict(s) reached
08:52:43 === [ci] control `end-keeps-inflight`
08:52:44 [ci] control `end-keeps-inflight`: 1 verdict(s) reached
08:52:44 === [ci] userland/acpiserver
08:52:45 [ci] userland/acpiserver: cargo test --manifest-path userland/acpiserver/Cargo.toml --target aarch64-apple-darwin
08:52:45 === [ci] userland/acpiserver/aml
08:52:51 [ci] userland/acpiserver/aml: cargo test --manifest-path userland/acpiserver/aml/Cargo.toml --target aarch64-apple-darwin
08:52:51 === [ci] userland/calc
08:52:56 [ci] userland/calc: cargo test --manifest-path userland/calc/Cargo.toml --target aarch64-apple-darwin
08:52:56 === [ci] userland/compositor/desktop
08:52:57 [ci] userland/compositor/desktop: cargo test --manifest-path userland/compositor/desktop/Cargo.toml --target aarch64-apple-darwin
08:52:57 === [ci] userland/diskserver
08:52:58 [ci] userland/diskserver: cargo test --manifest-path userland/diskserver/Cargo.toml --target aarch64-apple-darwin
08:52:58 === [ci] userland/fileserver
08:53:01 [ci] userland/fileserver: cargo test --manifest-path userland/fileserver/Cargo.toml --target aarch64-apple-darwin
08:53:01 === [ci] userland/logkeeper
08:53:02 [ci] userland/logkeeper: cargo test --manifest-path userland/logkeeper/Cargo.toml --target aarch64-apple-darwin
08:53:02 === [ci] userland/netstack
08:53:03 [ci] userland/netstack: cargo test --manifest-path userland/netstack/Cargo.toml --target aarch64-apple-darwin
08:53:03 === [ci] userland/netstack/mdns
08:53:04 [ci] userland/netstack/mdns: cargo test --manifest-path userland/netstack/mdns/Cargo.toml --target aarch64-apple-darwin
08:53:04 === [ci] userland/pkg
08:53:06 [ci] userland/pkg: cargo test --manifest-path userland/pkg/Cargo.toml --target aarch64-apple-darwin
08:53:06 === [ci] userland/soundserver
08:53:08 [ci] userland/soundserver: cargo test --manifest-path userland/soundserver/Cargo.toml --target aarch64-apple-darwin
08:53:08 === [ci] userland/soundserver/mixer
08:53:19 [ci] userland/soundserver/mixer: cargo test --manifest-path userland/soundserver/mixer/Cargo.toml --target aarch64-apple-darwin
08:53:19 === [ci] userland/sshserver
08:53:31 [ci] userland/sshserver: cargo test --manifest-path userland/sshserver/Cargo.toml --target aarch64-apple-darwin
08:53:31 === [ci] userland/symbolize
08:53:32 [ci] userland/symbolize: cargo test --manifest-path userland/symbolize/Cargo.toml --target aarch64-apple-darwin
08:53:32 === [ci] the apps for linux
08:53:51 [ci] the apps for linux: 10 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
08:53:51 === [ci] the apps for macos
08:53:58 [ci] the apps for macos: 10 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
08:53:58 === [ci] the apps for windows
08:54:15 [ci] the apps for windows: 10 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
08:54:15 === [ci] the toyos SDK
08:54:17 [ci] the toyos SDK: cargo test --manifest-path toyos/Cargo.toml --target aarch64-apple-darwin
08:54:17 === [ci] nothing left in $TMPDIR or /tmp
08:54:17 [ci] nothing left in $TMPDIR or /tmp: every test took its scratch with it
08:54:17 [ci] Host: 77 step(s), all green
EXIT=0

stage.log: the shared boot staged, no machine touched

$ cargo test --test toyos-build -- --metal --metal-readback <scratch>/metal abuse_cwd_growth  # at 54e8a0791
   Compiling ring v0.17.14
   Compiling rustls v0.23.45
   Compiling rustls-webpki v0.103.15
   Compiling ureq v3.4.2
   Compiling toyos-build v0.1.0 (<worktree>)
    Finished `test` profile [optimized + debuginfo] target(s) in 14.58s
     Running tests/toyos.rs (target/debug/deps/toyos_build-1b8e461cc3d9c78f)
08:54:38   BUILD x86_64 binaries of tests/toyos-rust-tests
08:54:38 external deps changed: cleaning <worktree>/tests/toyos-rust-tests/tls-dlopen-lib
     Removed 0 files
08:54:38 external deps changed: cleaning <worktree>/tests/toyos-rust-tests/tls-lib
     Removed 0 files
08:54:39 external deps changed: cleaning <worktree>/tests/toyos-rust-tests/tls-multi-crate
     Removed 0 files
08:54:39 external deps changed: cleaning <worktree>/tests/toyos-rust-tests/tls-cranelift
     Removed 0 files
08:55:58   BUILT x86_64 binaries of tests/toyos-rust-tests  (80s)
08:55:58 [toyos] Compiling 137 C tests, and attempting 24 declared ones...
08:56:02 [toyos] no metal registration matches filter Some("abuse_cwd_growth"); the shared boots' members are not filtered by name
08:56:02 [metal] 0 registration(s) and 1 shared member(s) over 1 boot(s)
08:56:02   BUILD x86_64 kernel, loader, ROOT of <scratch>/metal/shared
08:56:06   BUILT x86_64 kernel, loader, ROOT of <scratch>/metal/shared  (4s)
08:56:06 [metal] shared: 1 job(s), armed with [] — <scratch>/metal/shared/image.img
# One boot per image. Each invocation is `cargo <words>` from this worktree.

shared
  image: <scratch>/metal/shared/image.img
  cargo run --bin toyos-metal -- --image <scratch>/metal/shared/image.img --readback <scratch>/metal/shared --fat32-check
08:56:06 [metal] staged 1 image(s); <scratch>/metal/request.txt lists them. The machine was not touched, so this run establishes nothing about it.
error: test failed, to rerun pass `--test toyos-build`

Caused by:
  process didn't exit successfully: `<worktree>/target/debug/deps/toyos_build-1b8e461cc3d9c78f --metal --metal-readback <scratch>/metal abuse_cwd_growth` (exit status: 2)
EXIT=2

@Japabu Japabu changed the title chdir drops the VFS guard before taking process-data, matching open's lock order chdir ends its VFS guard before taking process-data, the order open takes Oct 8, 2026
@Japabu

Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

T14 at head 54e8a0791 (orchestrator's run; the image staged by the implementer from the clean tree, sha256 449cd163…93dc4ea7, checked against the request and printed by the command that flashed it):

boot member boot judge (--metal --metal-readback … abuse_cwd_growth, clean tree at the head)
shared test_rs_abuse_cwd_growth alone exit 0 exit 0, [metal] 1 passed, 0 failed, 1 boot(s); ===TEST_END test_rs_abuse_cwd_growth exit=0===

@Japabu

Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 2 of wt/toyos-chdirlock at 54e8a0791548f31c0a95d5ba64a1c07a1dd4b233 against origin/main 2e781a49d (merge base 017451624). Read only: I ran no build, no lint and no test; every count below is read from the implementer's and the orchestrator's logs.

Net: 3 files, +15 −1. Production +3 −1 (kernel/src/syscall/fs.rs, one line of it the comment); tests 0; issues/ +12. The closed issue file is added by 7a1ad760f and deleted by 96a25fe4b and nets to nothing; its slug is cited nowhere at the head or on origin/main.

Round 1's BLOCKERs

  • cargo run -- --ci host absent: CLOSED. ci-host.log (484,698 bytes, 8,950 lines) opens $ cargo run -- --ci host # at 54e8a0791548f31c0a95d5ba64a1c07a1dd4b233 and ends [ci] Host: 77 step(s), all green / EXIT=0. Its FAILED lines all sit inside the control steps, each of which closes "N verdict(s) reached". The body names the command, the exit and the head, and links the step lines.
  • No test that executes sys_chdir: CLOSED, by the orchestrator's T14 run at this head. Image sha256 449cd163…93dc4ea7 in request.txt, staged at 54e8a0791 (stage.log, exit 2, "staged"), is the one the boot reports. metal/judge.log: [metal] 1 passed, 0 failed, 1 boot(s); metal/shared/kernel.log:359-377: TEST_START test_rs_abuse_cwd_growth … exit: test_rs_abuse_cwd_growth pid=10 code=0 … TEST_END … exit=0; verdict.txt: passed. The test reaches both rewritten arms (set_current_dir refused at abuse_cwd_growth.rs:74/106, accepted at :119/160/176, read back through getcwd). The body's claim that no QEMU boot runs a chdir holds for what I read: the only callers are that test and the shell (userland/shell/src/main.rs:32, :610), :32 is skipped under -c, which is how every test binary launches it, and no tests/*/system.toml a QEMU test boots starts terminal or console.
  • The oracle's result contradicted by its logs: CLOSED. oracle-red.log: 6 previous errors, EXIT=101, at arch/x86_64/idt/mod.rs:470, loader/mod.rs:748, user_ptr.rs:42, syscall/fs.rs:44, syscall/fs.rs:86, syscall/vm.rs:233. oracle-green.log: 5 previous errors, EXIT=101, the same without fs.rs:86. The body's table says exactly that, both logs are whole in the evidence comment, and both are of the head's form (fix.patch is the head's hunk; oracle.sh reverses it for red with revert rc=0). Each of the five residual readings is true of the tree: idt/mod.rs:470-475 writes entries and takes no lock; loader/mod.rs:748-754 and vm.rs:233-239 hold the guard across a log! and take no process-data, and log::emit takes no lock once the drain thread runs (kernel/src/log/mod.rs, "emit may take no lock"); user_ptr.rs:42 returns; fs.rs:44-47 moves or returns.

Round 1's NOTEs

  • The fix in the tree's form: CLOSED. kernel/src/syscall/fs.rs:86-88 is the comment, let resolved = vfs::lock().cd(&cwd, path); and match resolved, the form of fs.rs:37 and loader/mod.rs:374, :743. The arms are main's, untouched.
  • The measurement recorded: CLOSED. issues/clippy-stage-two-is-lints-one-at-a-time.md:131 carries 6 and 5, the target, the features and the one guard marked, the five sites by name, what is unmeasured and what the lint does not see; ADOPTED does reach the host workspace's shapes (src/clippy.rs:75-187, :204) and undocumented_unsafe_blocks is the per-shape -W it cites (:165, :170, :195). file_cache::touch (:145), scheduler::remove_vruntime (:275) and inbox's answer (inbox/mod.rs:590) are the shape it says and are in neither log. issues/a-user-copy-demand-pages-under-whatever-its-caller-holds.md:19-23 is true: ops::open takes vfs::lock() (object/ops.rs:95) inside sys_open's with_process_data.
  • The dropped exit clause: CLOSED. The body says the staged test is not added and why.
  • "Priority (a) in the brief": CLOSED. Gone from the body.

BLOCKER

None.

NOTE

  • PR body, "Gates", second bullet, and "Unsure of", first bullet — stale against the record: "staged, not run", "the T14 run is the orchestrator's and is owed before landing" and "Until the T14 run, the two rewritten arms have been … not executed" predate the T14 reading in the PR's last comment. The body owes that reading in their place: shared boot, test_rs_abuse_cwd_growth alone, boot exit 0, judge exit 0, [metal] 1 passed, 0 failed, 1 boot(s), at 54e8a0791 — prose, the orchestrator's edit.
  • issues/a-user-copy-demand-pages-under-whatever-its-caller-holds.md:25-27 — the exit's second branch still reads "none of the three" under a body that now names four; the line that folds to a site on that branch is user_ptr.rs's and says nothing of the VFS/process-data order — prose.

What the landing rests on

No CI has run at this head: host, toolchain and guest are all SKIPPED, the pull request being a draft. The host gate above is one local run on macOS, on the branch as it stands and not on its merge with main. The QEMU guest suite has not been run on this kernel anywhere; the body says so and claims nothing from it. main has moved five issues/ files since the merge base, none of them this branch's, and the merge is clean. The landing therefore rests on host and guest / suite on the ready pull request and in the merge queue, the first run of either on the merged tree.

LAND

@Japabu
Japabu marked this pull request as ready for review October 8, 2026 09:02
@Japabu
Japabu enabled auto-merge October 8, 2026 09:02
@Japabu
Japabu added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit 07531c3 Oct 8, 2026
6 checks passed
@Japabu
Japabu deleted the wt/toyos-chdirlock branch October 8, 2026 09:55
Japabu added a commit that referenced this pull request Oct 8, 2026
…ss-data

No overlap with this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Japabu added a commit that referenced this pull request Oct 8, 2026
Brings in #749, #757, #759 and #762. #762 changes kernel/src/object/mod.rs
and toyos-abi/src/syscall.rs in hunks disjoint from this branch's.

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