Skip to content

blockd sets its ring cursors before it answers an open - #643

Merged
Japabu merged 7 commits into
mainfrom
wt/toyos-blockdreplay
Oct 2, 2026
Merged

Japabu merged 7 commits into
mainfrom
wt/toyos-blockdreplay

Conversation

@Japabu

@Japabu Japabu commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Head a5654353f. origin/main 46af79d5d is merged in at c0c12408a, without conflict. Net against it: 9 files, +166 −43. Production +55 −14 (wire.rs, layout.rs, blockd's main.rs and session.rs; 25 of the added lines are doc comments, the two doc-tests among them). Tests +23 −1 (model.rs). Issues +88 −28.

The defect, still on main

blockd answers MSG_OPENED and only then makes its ends of the session page: admit calls layout::server after handshake has sent the answer (userland/blockd/src/main.rs:331 at 46af79d5d). A fresh region is zero, so a first open cannot show it. A reconnect sends the page the last server wrote, whose request-ring head and completion-ring tail are still that server's, and Session::reconnect pumps the moment it hears. A client that gets there before admit:

  • has no room to ask: the stale head is past its zeroed tail, Violation::HeadPastTail;
  • pops the dead session's completions, and the first names a tag it no longer has in flight: Violation::Tag, the session ends, and the caller's call is Io.

No acknowledged write is lost to it: the end requeues every acknowledged, unflushed write. fsd reconnects on Ended and meets the same race.

What changed

The order is a type. wire::Opened's fields are private. Opened::over(page, blocks, unique) returns a server's ends of the page and its answer together, and layout::server is pub(crate). blockd's Opening carries both, so it has no MSG_OPENED to encode before its two cursors are 0. Opened::decode is the client's way to one.

Two tests.

  • A compile-fail case on Opened: a literal outside the crate does not compile, beside a block that makes one with Opened::over. It names no error code: the block beside it is what holds it to its reason, as in toyos-net-wire.
  • a_used_page_reads_fresh_once_a_servers_ends_are_made_over_it (toyos-blockring/src/model.rs): over the page a crashed model run leaves, a client's ends alone get HeadPastTail asking and the last session's completion hearing, which Client::complete refuses with Tag. Once a server's ends are made the page reads fresh. It holds nothing of blockd's order.

The reorder issue closes, on #534. issues/filesystem/blockd-survives-its-death-reds-on-a-replacement-that-reorders-acknowledged-writes.md is deleted, and not on the type: Opened::over orders the handshake and bears on neither arm of its exit. 17bb26416 (#534) is the fix for its signature. That commit's message names run 36273557690 and the pair [282600, 281592] / [281592, 282600], and its judge holds a replacement to the same writes, in order only among writes that overlap. git merge-base --is-ancestor 17bb26416 <head> exits 1 for 8c5be843, a55d62c6 and 59bd29ff2, the heads the issue cites and was filed at, and 0 for origin/main: every red it records is the verdict of the judge before #534. git grep of the slug at this head exits 1: nothing cites it. Its durable rule is at its site already (toyos-blockring/src/lib.rs, "Requests in flight at once are unordered", and Client::next_request).

Three issues filed.

  • issues/filesystem/nothing-reopens-a-blockd-session-over-a-used-page.md: the nightly's blockd_io: FAIL /AFTER.BIN after the restart: Io (run 36753172688, guest 7) was fixed by reading and is reproduced by nothing. Its exit is the restart test Stage D owes, red with that Io under blockd's order reverted.
  • issues/build/a-compile-fail-case-names-an-error-code-rustdoc-never-reads.md: rustdoc reads the code only on a nightly build, and the host suites run a stable one. E0603 in toyos-bootmap and E0080 in toyos-transport changed to E0308 leave both crates' doc-tests at EXIT=0.
  • issues/build/the-guest-test-crate-depends-on-three-crates-no-test-uses.md: off this task, found while searching for the crate's users. 520c0d129 deleted blockd_io.rs and left its three dependencies in tests/toyos-rust-tests/Cargo.toml.

Checks: a device server on a filesystem's path to its disk

Negative control, arm B. At 0d2642da2, the whole fix reverted onto 46af79d5d's production files with the tests kept: userland/blockd/src/main.rs, session.rs and toyos-blockring/src/layout.rs equal origin/main's (git diff --quiet, EXIT=0), and wire.rs is origin/main's plus the fifteen doc lines of the two doc-tests.

  • cargo test -p toyos-blockring --doc: EXIT=101. wire::Opened (line 39) - compile fail ... FAILED, "Test compiled successfully, but it's marked compile_fail." The block beside it fails too, because over is gone; that proves nothing.
  • cargo test -p toyos-blockring --lib: EXIT=0, 20 pass. The model test does not hold blockd's order; the type does.
  • blockd's host test: EXIT=0. The reverted tree is main's blockd, and it builds.

Arm C. blockd's main.rs as on origin/main, against the crate at the head: EXIT=101, it does not build. E0603 function server is private at main.rs:331, E0616 field blocks ... is private at main.rs:723.

Green arm. cargo test -p toyos-blockring: EXIT=0, 20 unit tests and both doc-tests.

Oracle. For the order: rustc's privacy check, a checker the author did not write. For what a used page holds and what the transport answers over it: the ring's interleaving model, whose walk drives the crate's own Client and ServerSession and the transport's own rings to the page the test reads. The model does not cover the order (arm B, --lib EXIT=0). No recorded failure is reproduced: nothing reproduces the nightly's Io, and that is filed.

Mutations, each a checked patch, run and restored:

mutation command exit
Consumer::new stores no head cargo test -p toyos-blockring --lib 101 the model test alone, model.rs:791, Err(HeadPastTail) for Ok(4)
cargo test -p toyos-transport 0
Producer::new stores no tail cargo test -p toyos-blockring --lib 101 the model test, model.rs:792, and three model runs
cargo test -p toyos-transport 0
both of Opened's fields pub cargo test -p toyos-blockring --doc 101 the compile-fail case alone
one of them pub cargo test -p toyos-blockring --doc 0 the literal is still refused
E0603 to E0308, toyos-bootmap cargo test -p toyos-bootmap --doc 0 filed
E0080 to E0308, toyos-transport cargo test -p toyos-transport --doc 0 filed

Every patch, the script that applied, ran and reversed each, the logs and the exits: #643 (comment)

What the tests see that reading cannot. The compile-fail case: a pub on both fields builds everywhere and reds only there. One pub field leaves the literal refused and the case green, and the order held. The model test: what the transport's cursor arithmetic answers over a page a crashed session left, which is a run's result; no test in toyos-transport holds that either constructor stores its cursor.

A session opening at 0d2642da2. cargo test --test toyos-build -- iommu_virtio_platform --nocapture: EXIT=0, three boots.

[serial 0] {0.357 blockd} blockd: NVMe up: 4 I/O queues of 63 commands, volatile write cache present, so a flush issues Flush, 512-byte sectors, 262144 sectors
[serial 0] {0.367 blockd} blockd: partition 639A73DC-ED31-4B0D-8A47-8DE4144FCC48 at block 256, 32256 blocks
[serial 0] {0.375 blockd} blockd: session 0 opened 639A73DC-ED31-4B0D-8A47-8DE4144FCC48 (32256 blocks)
[serial 0] {0.381 fsd} fsd: block 0 designates this partition for ToyOS; formatting it
[serial 0] {0.383 fsd} fsd: Data serving /apps, /config, /home, /state — bcachefs, 0 names, 0 files open; cache 4 blocks (4 dirty), 6 of 8 reads hit

fsd read the partition through session 0 and formatted it. The third boot (tests/testcases) prints the same lines for its own partition. The second, tests/netcase with no unit, is the run's own control: blockd's claim is refused, it prints blockd: NOT SERVING and no session line, and fsd says DATA is absent this boot. The run's lines per boot: #643 (comment)

This is a first open over a fresh region. main's blockd opens one too: the run shows the changed path runs and carries requests, and cannot tell the order.

Gates

a5654353f is 0d2642da2 plus the owner line of one issue file (git diff --stat 0d2642da2 a5654353f: 1 file, +2). Nothing cites that file: git grep of its slug at a5654353f exits 1. At a5654353f:

command exit
cargo run -- --ci host 0 "Host: 59 step(s), all green"; both doc-tests, the model test and blockd's host test are in its log; tree clean before and after

At 0d2642da2:

command exit
cargo run -- --ci host 0 "Host: 59 step(s), all green"; both doc-tests, the model test and blockd's host test are in its log
cargo run -- --build-only 0
cargo test --test toyos-build 0 "21 passed, 21 total", 12 wide
cargo test --test toyos-build -- iommu_virtio_platform --nocapture 0 "1 passed, 1 total"
cargo test -p toyos-blockring 0
cargo test --manifest-path userland/blockd/Cargo.toml --target aarch64-apple-darwin 0

0d2642da2 is 9a2ea85e9 plus one issue file. Every gate of this table and every control and mutation above ran at both heads with the same exits; at 9a2ea85e9 the build compiled blockd, fsd and init for ToyOS from a clean userland target.

git status --porcelain --ignore-submodules=none is empty after them. No fork or checkout names the crate: grep -rl for toyos_blockring and toyos-blockring over ~/.cargo/git/checkouts, rust/library and rust/src/bootstrap exits 1. In the tree its users are blockd, fsd and init, and all three build.

No guest test is added or changed, and no T14 run is requested: the type is the cheapest tier that reaches the order, and a guest's capture reaches blockd's session line.

Unsure

  • That the nightly's Io was this race is not proven, and nothing reopens a session over a used page. Both are issues/filesystem/nothing-reopens-a-blockd-session-over-a-used-page.md. The host test reaches HeadPastTail and Tag; that the client's glue turns them into an ended session and Io is read off Session::pump, drain and wait, which no host runs.
  • The compile-fail case passes on any compile error. What ties it to the private fields is the mutation with both public, and the block beside it, which reds if Opened or its path moves. A literal that stopped compiling for another reason, a renamed field, would pass it.
  • The type refuses the literal, not a forgery: a server could hand-write the answer's bytes, or decode them.

🤖 Generated with Claude Code

https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm

`blockd_survives_its_death` has two red signatures. Neither is a write
blockd lost or reordered; the second is a defect in blockd's open, fixed
here.

1. "the blockd after it wrote [281592, 282600] first" (the deleted issue).
   Every red it cites predates the judge that reads it now: 8c5be84 and
   a55d62c do not contain 17bb264 (#534, "a reissue keeps the order of
   the writes that overlap"), and the issue was filed at 59bd29f, which
   does not either; its branch merged #534 thirteen minutes later
   (672c499). The two writes are one block each, 1008 sectors apart, so
   the judge since #534 takes [281592, 282600] as a reissue of
   [282600, 281592]. What reorders them is QEMU, not blockd:
   - the client reissues in first-acknowledged order and puts the two on
     the wire together, since they do not overlap (`Client::next_request`
     keeps an overlapping pair apart: `overlapping_writes_go_out_again_in_order`);
   - blockd's `roomiest` takes the last of equally empty queues, so the
     first goes down SQ 4 (the nightly log's "WITHHELD ... queue 4
     identifier 0" is that choice) and the second down SQ 3;
   - QEMU v11.1.1's `nvme_process_db` schedules each SQ's bottom half, and
     `aio_bh_enqueue` inserts at the list's head while `aio_bh_poll`
     dequeues from it: two doorbells rung before the main loop polls run
     their SQs last-first, and `pci_nvme_write` is traced as each SQ is
     processed. So the trace reads [281592, 282600] exactly when both
     doorbells land inside one poll.
   NVMe promises no order between commands outstanding at once (NVM
   Express 1.4, §6.3 "Command Ordering Requirements": each command "is
   processed as an independent entity without reference to other commands
   submitted to the same I/O Submission Queue or to commands submitted to
   other I/O Submission Queues", and a host that needs an order enforces
   it above the controller), and two writes to disjoint blocks leave the
   same medium in either order. Across the
   orchestrator's logs on this host, 32 passes, all TCG, every one
   tracing [282600, 281592]; CI's reorder is KVM's timing. The version of
   Debian sid's QEMU on CI was not measured.

2. "blockd_io: FAIL /AFTER.BIN after the restart: Io" (nightly
   36753172688, guest 7). blockd answered MSG_OPENED and only then made
   its ends of the session page (`admit` called `layout::server`). A
   fresh region is zero, so a first open cannot tell; a reconnect sends
   the page its last server wrote, whose SQ head and CQ tail are still
   that server's. `Session::reconnect` pumps the moment it hears, and its
   first wait drains: a client that gets there before blockd's `admit`
   finds no room to ask (the stale head is past its zeroed tail) and a
   completion ring holding the dead session's completions. The first one
   names a tag the client no longer has in flight, `Client::complete`
   answers `Violation::Tag`, `Session::wait` ends the session, and the
   FAT32 call on it is `Io`. In the log, the relay of the replacement
   blockd's (pid 14) output had reached its partition table at 23.909 and
   not its "session 0 opened" line, which blockd prints after `admit`,
   when the client failed at 23.909: consistent with the race, not proof
   of it.
   No acknowledged write is lost to this: the session's end requeues
   every acknowledged, unflushed write for the next reconnect. fsd
   reconnects on `Ended` and would meet the same race.

The fix: `Service::open` makes the server's rings and the `Opening`
carries them, so no answer is sent before the cursors are zero, and
`admit` takes them from it. `layout::server` states the contract.

The model (`toyos-blockring/src/model.rs`) used to zero all four cursors
at a session's end, which no end does: the page now keeps each cursor
where its end left it until the next session's two ends set theirs, and
a ring is held to its queue only while an end is looking at it. New host
test `the_new_server_sets_its_cursors_before_the_client_looks`: over the
page a crashed model run leaves, a client looking before the new
server's ends are made gets `HeadPastTail` asking and the last session's
completion hearing, which `Client::complete` refuses with `Tag`; after,
the page is a fresh one. With the model's session end zeroing the page
again (a checked patch, restored), that test is FAILED (EXIT=101).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Japabu and others added 2 commits October 2, 2026 10:13
One conflict, modify/delete: this branch deleted
issues/filesystem/blockd-survives-its-death-reds-on-a-replacement-that-reorders-acknowledged-writes.md
and main (45db225) took one sentence out of it, the `--known-red` answer.
Main's file is kept whole, so that hunk stands: #660 cut the test the
issue's exit names, and the issue no longer closes here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
…ue stays open

The first round's fix moved `layout::server` from `admit` into
`Service::open`, where nothing but reading holds it: with that move
reverted at 0878aa1 and the round's tests kept, `cargo test -p
toyos-blockring` and blockd's host test both exit 0. The only arm that could
red was a guest run of `blockd_survives_its_death`, which #660 cut.

The order is now a type. `wire::Opened`'s fields are private and
`Opened::over(page, blocks, unique)` returns a server's ends and its answer
together; `layout::server` is the crate's own. blockd can encode no
`MSG_OPENED` before its two cursors are 0, and its `Opening` carries both.
A compile-fail case on `Opened` holds the refusal (E0451), beside a block
that compiles.

The issue is not closed. Its exit asks for the test green across a run of
repeats, and the test and its trace judge left the suite in 520c0d1, so
nothing that runs can meet it. Its body now says what the first round found
(every cited red predates #534's judge, whose comparison passes the cited
trace: run on the two orders, it passes where the comparison before it
fails) and where the test went.

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

Japabu commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

Controls and mutations at 7a8c3b21e. Each patch was checked with git apply --check, applied, built and run, and reversed by the script at the end, which also shows the tree clean of it after. Exits are each command's own.

arm patch command exit what the log says
green none cargo test -p toyos-blockring 0 20 unit tests and both doc-tests pass
green none cargo test --manifest-path userland/blockd/Cargo.toml --target aarch64-apple-darwin 0
0 revert-v1-production, at 0878aa112 cargo test -p toyos-blockring 0 the first round's fix reverted with its tests kept: nothing reds
0 same blockd's host test 0
B fix-reverted-tests-kept cargo test -p toyos-blockring --doc 101 wire::Opened (line 39) - compile fail ... FAILED: "Test compiled successfully, but it's marked compile_fail." The block beside it also fails, E0599, because over is gone: that one proves nothing.
B same cargo test -p toyos-blockring --lib 0 20 pass: no unit test and no model run sees the revert
B same blockd's host test 0 main's blockd builds against main's crate
C blockd-at-main blockd's host test 101 does not build: E0603 function server is private at main.rs:331, E0616 field blocks ... is private at main.rs:723
m1 m1-model-end-zeroes-the-page cargo test -p toyos-blockring --lib 101 the_new_server_sets_its_cursors_before_the_client_looks ... FAILED at model.rs:799, Ok(4) for Err(HeadPastTail); 19 pass
m2 m2-consumer-new-stores-no-head same 101 the same test FAILED at model.rs:805, Err(HeadPastTail) for Ok(4); 19 pass
m3 m3-producer-new-stores-no-tail same 101 the same test FAILED at model.rs:806, and three model runs on the completion ring gave what the queue does not hold; 16 pass

Arm B's production files: git diff --quiet origin/main over userland/blockd/src/main.rs, userland/blockd/src/session.rs and toyos-blockring/src/layout.rs exits 0 with the patch applied, and wire.rs differs from origin/main's by the fifteen doc lines of the two doc-tests and nothing else.

The exits file:

blockring EXIT=0
blockd EXIT=0
done
head 7a8c3b21e8de65f589b62bfad9efe99088d958cc
green-blockring: EXIT=0 (cargo test -p toyos-blockring)
green-blockd: EXIT=0 (cargo test --manifest-path userland/blockd/Cargo.toml --target aarch64-apple-darwin)
armB: blockd's two files and layout.rs equal origin/main's: EXIT=0
armB-doc: EXIT=101 (cargo test -p toyos-blockring --doc)
armB-lib: EXIT=0 (cargo test -p toyos-blockring --lib)
armB-blockd: EXIT=0 (cargo test --manifest-path userland/blockd/Cargo.toml --target aarch64-apple-darwin)
armC-blockd: EXIT=101 (cargo test --manifest-path userland/blockd/Cargo.toml --target aarch64-apple-darwin)
m1-model-end-zeroes-the-page: EXIT=101 (cargo test -p toyos-blockring --lib)
m2-consumer-new-stores-no-head: EXIT=101 (cargo test -p toyos-blockring --lib)
m3-producer-new-stores-no-tail: EXIT=101 (cargo test -p toyos-blockring --lib)
status after: [ M rust]
done
revert-v1-production.patch
diff --git a/toyos-blockring/src/layout.rs b/toyos-blockring/src/layout.rs
index 0029ce7cd..05f60c90d 100644
--- a/toyos-blockring/src/layout.rs
+++ b/toyos-blockring/src/layout.rs
@@ -62,9 +62,6 @@ pub fn client<W: Word>(page: &[W; RING_WORDS]) -> ClientRings {
 
 /// The server's ends of a session page it was sent, every word it owns set to
 /// 0. Whatever the client left in its own is bounded when first looked at.
-///
-/// Made before the open is answered: a client that reconnects sends the page
-/// its last server wrote, and reads these two words the moment it hears.
 pub fn server<W: Word>(page: &[W; RING_WORDS]) -> ServerRings {
     (Consumer::new(page, REQUESTS), Producer::new(page, COMPLETIONS))
 }
diff --git a/userland/blockd/src/main.rs b/userland/blockd/src/main.rs
index 731467cd5..09a3aa282 100644
--- a/userland/blockd/src/main.rs
+++ b/userland/blockd/src/main.rs
@@ -247,9 +247,6 @@ struct Service {
 /// A session decided on and not yet told to its client.
 struct Opening {
     region: Region,
-    /// Made before the client is told: it reads them the moment it hears, and
-    /// a region it opens again holds the last server's until they are.
-    rings: ServerRings,
     device_addr: u64,
     first: u64,
     blocks: u64,
@@ -306,8 +303,7 @@ impl Service {
         let (first, blocks) = self.place(guid)?;
         match self.ctrl.up().claim().dma_map(region.handle()) {
             Ok(mapping) if mapping.bytes == SESSION_BYTES as u64 => {
-                let rings = layout::server(region.words());
-                Ok(Opening { region, rings, device_addr: mapping.device_addr, first, blocks, unique: guid })
+                Ok(Opening { region, device_addr: mapping.device_addr, first, blocks, unique: guid })
             }
             // A region longer than a session would spend the claim's bound on
             // the kernel's side for every other client: refused whole.
@@ -332,13 +328,14 @@ impl Service {
     fn admit(&mut self, opening: Opening, conn: Connection) -> u64 {
         let id = self.next_id;
         self.next_id += 1;
+        let rings = layout::server(opening.region.words());
         self.sessions.insert(
             id,
             Served {
                 conn,
                 region: opening.region,
                 device_addr: opening.device_addr,
-                rings: opening.rings,
+                rings,
                 state: ServerSession::new(opening.first, opening.blocks),
                 unique: opening.unique,
                 closing: false,
fix-reverted-tests-kept.patch
diff --git a/toyos-blockring/src/layout.rs b/toyos-blockring/src/layout.rs
index 56f65d701..05f60c90d 100644
--- a/toyos-blockring/src/layout.rs
+++ b/toyos-blockring/src/layout.rs
@@ -62,8 +62,7 @@ pub fn client<W: Word>(page: &[W; RING_WORDS]) -> ClientRings {
 
 /// The server's ends of a session page it was sent, every word it owns set to
 /// 0. Whatever the client left in its own is bounded when first looked at.
-/// A server's way to them is [`crate::wire::Opened::over`].
-pub(crate) fn server<W: Word>(page: &[W; RING_WORDS]) -> ServerRings {
+pub fn server<W: Word>(page: &[W; RING_WORDS]) -> ServerRings {
     (Consumer::new(page, REQUESTS), Producer::new(page, COMPLETIONS))
 }
 
diff --git a/toyos-blockring/src/wire.rs b/toyos-blockring/src/wire.rs
index fadee718f..651549139 100644
--- a/toyos-blockring/src/wire.rs
+++ b/toyos-blockring/src/wire.rs
@@ -5,16 +5,6 @@
 //! with it, and is answered [`MSG_OPENED`] or [`MSG_REFUSED`] with a
 //! [`Refusal`]. After `MSG_OPENED` the connection carries nothing but doorbell
 //! bytes, each way.
-//!
-//! **The answer comes with the server's ends of the page.** A client looks at
-//! the page the moment it hears, and one that opens the same region again
-//! sends the cursors its last server left on it. So [`Opened::over`] makes a
-//! server's ends and its answer together: nothing else makes the ends, and an
-//! answer is otherwise only read off the wire ([`Opened::decode`]).
-
-use toyos_transport::Word;
-
-use crate::layout::{self, ServerRings, RING_WORDS};
 
 /// Open the partition whose unique GUID is the payload, over the region sent
 /// with it. Handles: the region.
@@ -51,28 +41,13 @@ pub const GUID_BYTES: usize = 16;
 /// ```
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub struct Opened {
-    blocks: u64,
-    unique: [u8; GUID_BYTES],
+    pub blocks: u64,
+    pub unique: [u8; GUID_BYTES],
 }
 
 impl Opened {
     pub const BYTES: usize = 8 + GUID_BYTES;
 
-    /// A server's ends of the session `page` it was sent, every word it owns
-    /// set to 0, and the answer to send after: `blocks` of the partition
-    /// `unique`.
-    pub fn over<W: Word>(page: &[W; RING_WORDS], blocks: u64, unique: [u8; GUID_BYTES]) -> (ServerRings, Self) {
-        (layout::server(page), Self { blocks, unique })
-    }
-
-    pub fn blocks(&self) -> u64 {
-        self.blocks
-    }
-
-    pub fn unique(&self) -> [u8; GUID_BYTES] {
-        self.unique
-    }
-
     pub fn encode(&self) -> [u8; Self::BYTES] {
         let mut out = [0u8; Self::BYTES];
         out[..8].copy_from_slice(&self.blocks.to_le_bytes());
diff --git a/userland/blockd/src/main.rs b/userland/blockd/src/main.rs
index b98b9a6cb..09a3aa282 100644
--- a/userland/blockd/src/main.rs
+++ b/userland/blockd/src/main.rs
@@ -55,7 +55,7 @@ use toyos_abi::part::{PartGuid, GUID_TEXT_LEN};
 use toyos_abi::syscall::{DEV_PREFIX, SyscallError};
 use toyos_blockhold::Holds;
 use toyos_blockring::entry::{Completion, Op};
-use toyos_blockring::layout::{ServerRings, DEPTH};
+use toyos_blockring::layout::{self, ServerRings, DEPTH};
 use toyos_blockring::server::{ServerSession, Taken};
 use toyos_blockring::wire::{self, Opened, Refusal};
 use toyos_blockring::{BLOCK_BYTES, PORT, SESSION_BYTES};
@@ -247,10 +247,10 @@ struct Service {
 /// A session decided on and not yet told to its client.
 struct Opening {
     region: Region,
-    rings: ServerRings,
-    opened: Opened,
     device_addr: u64,
     first: u64,
+    blocks: u64,
+    unique: [u8; 16],
 }
 
 impl Service {
@@ -303,8 +303,7 @@ impl Service {
         let (first, blocks) = self.place(guid)?;
         match self.ctrl.up().claim().dma_map(region.handle()) {
             Ok(mapping) if mapping.bytes == SESSION_BYTES as u64 => {
-                let (rings, opened) = Opened::over(region.words(), blocks, guid);
-                Ok(Opening { region, rings, opened, device_addr: mapping.device_addr, first })
+                Ok(Opening { region, device_addr: mapping.device_addr, first, blocks, unique: guid })
             }
             // A region longer than a session would spend the claim's bound on
             // the kernel's side for every other client: refused whole.
@@ -329,15 +328,16 @@ impl Service {
     fn admit(&mut self, opening: Opening, conn: Connection) -> u64 {
         let id = self.next_id;
         self.next_id += 1;
+        let rings = layout::server(opening.region.words());
         self.sessions.insert(
             id,
             Served {
                 conn,
                 region: opening.region,
                 device_addr: opening.device_addr,
-                rings: opening.rings,
-                state: ServerSession::new(opening.first, opening.opened.blocks()),
-                unique: opening.opened.unique(),
+                rings,
+                state: ServerSession::new(opening.first, opening.blocks),
+                unique: opening.unique,
                 closing: false,
                 requests: 0,
                 posted: false,
@@ -714,13 +714,13 @@ fn handshake(service: &mut Service, p: Pending, msg_type: u32, payload_len: usiz
             refuse(&p.conn, why);
         }
         Ok(opening) => {
-            let opened = opening.opened;
+            let opened = Opened { blocks: opening.blocks, unique: guid };
             if p.conn.try_send_bytes(wire::MSG_OPENED, &opened.encode()).is_err() {
                 service.abandon(opening);
                 return;
             }
             let id = service.admit(opening, p.conn);
-            println!("blockd: session {id} opened {} ({} blocks)", guid_text(guid), opened.blocks());
+            println!("blockd: session {id} opened {} ({} blocks)", guid_text(guid), opened.blocks);
         }
     }
 }
diff --git a/userland/blockd/src/session.rs b/userland/blockd/src/session.rs
index 8de1de510..e00613a55 100644
--- a/userland/blockd/src/session.rs
+++ b/userland/blockd/src/session.rs
@@ -159,7 +159,7 @@ impl Session {
 
     /// The partition's length in blocks.
     pub fn blocks(&self) -> u64 {
-        self.opened.blocks()
+        self.opened.blocks
     }
 
     /// The most requests this session has had on the wire at once.
blockd-at-main.patch
diff --git a/userland/blockd/src/main.rs b/userland/blockd/src/main.rs
index b98b9a6cb..09a3aa282 100644
--- a/userland/blockd/src/main.rs
+++ b/userland/blockd/src/main.rs
@@ -55,7 +55,7 @@ use toyos_abi::part::{PartGuid, GUID_TEXT_LEN};
 use toyos_abi::syscall::{DEV_PREFIX, SyscallError};
 use toyos_blockhold::Holds;
 use toyos_blockring::entry::{Completion, Op};
-use toyos_blockring::layout::{ServerRings, DEPTH};
+use toyos_blockring::layout::{self, ServerRings, DEPTH};
 use toyos_blockring::server::{ServerSession, Taken};
 use toyos_blockring::wire::{self, Opened, Refusal};
 use toyos_blockring::{BLOCK_BYTES, PORT, SESSION_BYTES};
@@ -247,10 +247,10 @@ struct Service {
 /// A session decided on and not yet told to its client.
 struct Opening {
     region: Region,
-    rings: ServerRings,
-    opened: Opened,
     device_addr: u64,
     first: u64,
+    blocks: u64,
+    unique: [u8; 16],
 }
 
 impl Service {
@@ -303,8 +303,7 @@ impl Service {
         let (first, blocks) = self.place(guid)?;
         match self.ctrl.up().claim().dma_map(region.handle()) {
             Ok(mapping) if mapping.bytes == SESSION_BYTES as u64 => {
-                let (rings, opened) = Opened::over(region.words(), blocks, guid);
-                Ok(Opening { region, rings, opened, device_addr: mapping.device_addr, first })
+                Ok(Opening { region, device_addr: mapping.device_addr, first, blocks, unique: guid })
             }
             // A region longer than a session would spend the claim's bound on
             // the kernel's side for every other client: refused whole.
@@ -329,15 +328,16 @@ impl Service {
     fn admit(&mut self, opening: Opening, conn: Connection) -> u64 {
         let id = self.next_id;
         self.next_id += 1;
+        let rings = layout::server(opening.region.words());
         self.sessions.insert(
             id,
             Served {
                 conn,
                 region: opening.region,
                 device_addr: opening.device_addr,
-                rings: opening.rings,
-                state: ServerSession::new(opening.first, opening.opened.blocks()),
-                unique: opening.opened.unique(),
+                rings,
+                state: ServerSession::new(opening.first, opening.blocks),
+                unique: opening.unique,
                 closing: false,
                 requests: 0,
                 posted: false,
@@ -714,13 +714,13 @@ fn handshake(service: &mut Service, p: Pending, msg_type: u32, payload_len: usiz
             refuse(&p.conn, why);
         }
         Ok(opening) => {
-            let opened = opening.opened;
+            let opened = Opened { blocks: opening.blocks, unique: guid };
             if p.conn.try_send_bytes(wire::MSG_OPENED, &opened.encode()).is_err() {
                 service.abandon(opening);
                 return;
             }
             let id = service.admit(opening, p.conn);
-            println!("blockd: session {id} opened {} ({} blocks)", guid_text(guid), opened.blocks());
+            println!("blockd: session {id} opened {} ({} blocks)", guid_text(guid), opened.blocks);
         }
     }
 }
m1-model-end-zeroes-the-page.patch
--- a/toyos-blockring/src/model.rs
+++ b/toyos-blockring/src/model.rs
@@ -148,6 +148,7 @@ impl Queues {
     fn ended(&mut self) {
         self.sq.clear();
         self.cq.clear();
+        self.reopen();
     }
 
     /// The next session over the same page: both ends set their cursors
m2-consumer-new-stores-no-head.patch
--- a/toyos-transport/src/queue.rs
+++ b/toyos-transport/src/queue.rs
@@ -155,7 +155,7 @@ pub struct Consumer<const E: usize, const D: u32, const N: usize> {
 impl<const E: usize, const D: u32, const N: usize> Consumer<E, D, N> {
     /// This end of the queue at `place`, its head stored 0.
     pub fn new<W: Word>(page: &[W; N], place: Place<E, D, N>) -> Self {
-        word(page, place.head).store(0, Ordering::Release);
+        let _ = word(page, place.head);
         Self { place, local: 0, released: 0, ready: 0 }
     }
 
m3-producer-new-stores-no-tail.patch
--- a/toyos-transport/src/queue.rs
+++ b/toyos-transport/src/queue.rs
@@ -105,7 +105,7 @@ pub struct Producer<const E: usize, const D: u32, const N: usize> {
 impl<const E: usize, const D: u32, const N: usize> Producer<E, D, N> {
     /// This end of the queue at `place`, its tail stored 0.
     pub fn new<W: Word>(page: &[W; N], place: Place<E, D, N>) -> Self {
-        word(page, place.tail).store(0, Ordering::Release);
+        let _ = word(page, place.tail);
         Self { place, local: 0, published: 0, room: D }
     }
 
arms.sh
#!/bin/sh
# Every arm and mutation of #643's round, at the head named below: each patch
# checked, applied, built and run, its exit recorded, and reversed, with the
# tree shown clean of it after.
W=/Users/jan/Dev/jan/toyos-blockdreplay
D=/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/643-round
HOST=aarch64-apple-darwin
cd $W || exit 2
OUT=$D/arms.exits
echo "head $(git rev-parse HEAD)" > $OUT

clean() { git status --porcelain | grep -v '^ M rust$'; }

arm() { # name patch command...
    name=$1; patch=$2; shift 2
    if [ -n "$(clean)" ]; then echo "$name: TREE NOT CLEAN BEFORE" >> $OUT; exit 2; fi
    git apply --check $patch || { echo "$name: PATCH DOES NOT APPLY" >> $OUT; exit 2; }
    git apply $patch
    "$@" > $D/$name.log 2>&1
    echo "$name: EXIT=$? ($*)" >> $OUT
    git apply -R $patch
    if [ -n "$(clean)" ]; then echo "$name: TREE NOT CLEAN AFTER" >> $OUT; exit 2; fi
}

# Green arm: the head as it stands.
cargo test -p toyos-blockring > $D/green-blockring.log 2>&1; echo "green-blockring: EXIT=$? (cargo test -p toyos-blockring)" >> $OUT
cargo test --manifest-path userland/blockd/Cargo.toml --target $HOST > $D/green-blockd.log 2>&1; echo "green-blockd: EXIT=$? (cargo test --manifest-path userland/blockd/Cargo.toml --target $HOST)" >> $OUT

# Arm B: the whole fix reverted onto origin/main's production files, the tests kept.
B=$D/armB/fix-reverted-tests-kept.patch
git apply --check $B && git apply $B
git diff --quiet origin/main -- userland/blockd/src/main.rs userland/blockd/src/session.rs toyos-blockring/src/layout.rs
echo "armB: blockd's two files and layout.rs equal origin/main's: EXIT=$?" >> $OUT
git diff origin/main -- toyos-blockring/src/wire.rs > $D/armB/wire-over-main.diff
git apply -R $B
arm armB-doc $B cargo test -p toyos-blockring --doc
arm armB-lib $B cargo test -p toyos-blockring --lib
arm armB-blockd $B cargo test --manifest-path userland/blockd/Cargo.toml --target $HOST

# Arm C: blockd's main.rs as on origin/main, against the crate at the head.
arm armC-blockd $D/armC/blockd-at-main.patch cargo test --manifest-path userland/blockd/Cargo.toml --target $HOST

# Mutations.
arm m1-model-end-zeroes-the-page $D/mut/m1-model-end-zeroes-the-page.patch cargo test -p toyos-blockring --lib
arm m2-consumer-new-stores-no-head $D/mut/m2-consumer-new-stores-no-head.patch cargo test -p toyos-blockring --lib
arm m3-producer-new-stores-no-tail $D/mut/m3-producer-new-stores-no-tail.patch cargo test -p toyos-blockring --lib

echo "status after: [$(git status --porcelain)]" >> $OUT
echo done >> $OUT
arm0/run.sh
#!/bin/sh
# Arm 0: the first round's fix (64f58547c) reverted at the merged head 0878aa112, its tests kept.
cd /Users/jan/Dev/jan/toyos-blockdreplay || exit 2
D=/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/643-round/arm0
git apply --check $D/revert-v1-production.patch || { echo "PATCH DOES NOT APPLY"; exit 2; }
git apply $D/revert-v1-production.patch
git diff --stat > $D/applied.stat
cargo test -p toyos-blockring > $D/blockring.log 2>&1; echo "blockring EXIT=$?" > $D/exits
cargo test --manifest-path userland/blockd/Cargo.toml --target aarch64-apple-darwin > $D/blockd.log 2>&1; echo "blockd EXIT=$?" >> $D/exits
git apply -R $D/revert-v1-production.patch
git status --porcelain > $D/status-after
echo done >> $D/exits

@Japabu

Japabu commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

One more mutation at 7a8c3b21e, and it stays green: the compile-fail case's error code changed from E0451 to E0308. cargo test -p toyos-blockring --doc: EXIT=0, both doc-tests ok. This host's rustdoc (1.98.1) does not check the code, so the block passes on any compile error; the body's Unsure says what ties it to the private fields. Checked, applied, run and reversed, the tree clean after.

--- a/toyos-blockring/src/wire.rs
+++ b/toyos-blockring/src/wire.rs
@@ -45,7 +45,7 @@ pub const GUID_BYTES: usize = 16;
 ///
 /// and never without them:
 ///
-/// ```compile_fail,E0451
+/// ```compile_fail,E0308
 /// let _answer = toyos_blockring::wire::Opened { blocks: 1, unique: [0; 16] };
 /// ```
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]

@Japabu Japabu changed the title blockd sets its ring cursors before it answers an open; the blockd reorder issue closes blockd sets its ring cursors before it answers an open Oct 2, 2026
@Japabu

Japabu commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

Review of 7a8c3b21e against origin/main de5f63cc2; no earlier review. Net: 6 files, +125 −33. Production +55 −14, 25 of the added lines doc and the two doc-tests; tests (toyos-blockring/src/model.rs) +51 −15; the issue +19 −4. The production growth is the type that carries the order: accepted.

BLOCKER

  • userland/blockd/src/main.rs:301-347,716-724 — nothing at this head runs the changed open path where anyone reads it — blockd's host test reaches place and listing only (main.rs:737), the 21 guests judge no line of a session, and the body says so: "No run at this head shows a blockd session opening", and of iommu_virtio_platform, "that is its config, not a line I read". One cheap run tells, and the branch goes back to take it: a --nocapture run of a guest whose boot starts blockd with its claim and fsd behind it (iommu_virtio_platform's headless arm is one), with its command, its exit, and the log's blockd: session 0 opened … (N blocks) and fsd's line for DATA in the body. If no guest's capture reaches that line, a T14 readback request for a row that does.
  • issues/filesystem/blockd-survives-its-death-reds-on-a-replacement-that-reorders-acknowledged-writes.md — changed against issues/README.md. Ruling: it closes in this pull request, by deletion, and not on the type. (a) Its subject was closed before it was filed: 17bb26416 (Main's nightly: the tarball in a TempDir, a join that keeps its answer, three judges that read the whole log #534) is the fix for this signature — its message names run 36273557690 and the pair [282600, 281592] / [281592, 282600], and its comparison (the same writes as a multiset, order held only among writes that overlap) passes that pair; git merge-base --is-ancestor 17bb26416 exits 1 for 8c5be843, a55d62c6 and 59bd29ff2, and 0 for origin/main. That is the exit's second arm, "the verdict is shown wrong about it". Opened::over orders the handshake and bears on neither arm. (b) The exit's last clause names an instrument 520c0d129 deleted, and line 117 of issues/build/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md already owes its replacement under its own exit; lines 37-40 make this file a second holder of that debt, waiting on a host test that need read no device trace and so can never judge this order. (c) As it stands, the slug and title claim a red the tree cannot produce and a reorder the body now gives to QEMU — README renames a refuted slug in the commit that corrects the body — under kind: defect, which is "real, reproducible". Delete the file: the slug is cited nowhere else in the tree at this head, and its durable rule is already at its site (toyos-blockring/src/client.rs:169-172, toyos-blockring/src/lib.rs:28-29).

NOTE

  • issues/ — what the branch does leave owed is filed nowhere: the nightly's blockd_io: FAIL /AFTER.BIN after the restart: Io (run 36753172688, guest 7) is fixed by reading and reproduced by nothing, and no tier reopens a session over a used page (the body's first two Unsure items; git grep 36753172688 7a8c3b21e -- issues/ finds nothing). One new file, kind: tooling, a measurement owed: that evidence, and an exit a run can fail — a restart test, at the tier Stage D builds it on, red with that Io under a mutation that moves blockd's two stores after its answer, and green without it.
  • toyos-blockring/src/wire.rs:49 — ruling on m4: no BLOCKER. What the case is for, both fields made pub, reds it (arm B, --doc EXIT=101); a block that fails for another reason takes a field rename a reader sees. But ,E0451 is read by nothing (m4: E0308 in its place, EXIT=0). Delete the code — the passing block beside it is the tree's guard for a compile-fail case (toyos-net-wire/src/lib.rs:204) — and file what m4 found: the same rustdoc runs toyos-bootmap/src/lib.rs:64 and toyos-transport/src/queue.rs:43,48,53, and the track's "compile-fail case" exits rest on it.
  • toyos-blockring/src/model.rs:146-158,192-201,286,478,649 — the ended/reopen split and hold_empty's two parameters change nothing the search visits (a state is keyed by its queues, and nothing looks at the page between notice and reconnect), and they serve one test that does not need them: on origin/main's model the page after crash already holds the dead server's cursors. Walk to crash, end the clone's session in the test, make the ends there; those hunks go, and m1 with them, a mutation of scaffolding this branch added. Or name the run that differs with them.
  • toyos-blockring/src/model.rs:792 — the name states an order the test cannot fail on: arm B, blockd's order reverted, --lib EXIT=0. Name it for what m2 and m3 turn red: a used page reads fresh once a server's ends are made over it.
  • PR body, "What the tests see that reading cannot" — "a later pub on either field … reds only there" is false of one field: the literal stays refused and the case green. It takes both.
  • PR body, "The issue is not closed" — "(both run on the two orders)" is a measurement with no command, exit or log, and the program behind judge-534.log is posted nowhere. With the issue closing on 17bb26416 the paragraph is rewritten; the sentence carries them or goes.

REMOVE

  • toyos-blockring/src/model.rs:789-790 — "as blockd makes them before it answers the open": the test holds nothing of blockd.

SEND BACK

Japabu and others added 2 commits October 2, 2026 10:56
origin/main is 46af79d: #669, #650 and #653 landed since de5f63c. No
conflict; none of them touches toyos-blockring, userland/blockd or the issue
this branch closes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
…test reads a used page

Answers the review of 7a8c3b2 on #643.

The issue `blockd-survives-its-death-reds-on-a-replacement-that-reorders-
acknowledged-writes` is deleted, and not on this branch's type. Its subject
was closed before it was filed: 17bb264 (#534) is the fix for its
signature. That commit's message names run 36273557690 and the pair
[282600, 281592] / [281592, 282600], and its judge holds the replacement to
the same writes as a multiset, in order only among writes that overlap.
`git merge-base --is-ancestor 17bb264 <head>` exits 1 for 8c5be84,
a55d62c and 59bd29f, the three heads the issue cites and was filed at, and
0 for origin/main: every red it records is the verdict of the judge before
#534, which is the exit's second arm, "the verdict is shown wrong about it".
`Opened::over` orders the handshake and bears on neither arm. The exit's last
clause named a test 520c0d1 (#660) deleted, and Stage D of
`the-guest-suite-runs-only-what-no-cheaper-tier-reaches` already owes its
replacement. The slug was cited nowhere else in the tree. Its durable rule is
at its site already: requests in flight at once are unordered
(`toyos-blockring/src/lib.rs`, and `Client::next_request`, which keeps an
overlapping pair apart). What reorders the two writes, QEMU's bottom halves
and NVM Express 1.4 section 6.3, is in 64f5854's message.

What the branch does leave owed is filed: `nothing-reopens-a-blockd-session-
over-a-used-page`. The nightly's `blockd_io: FAIL /AFTER.BIN after the
restart: Io` (run 36753172688, guest 7) was fixed by reading and is
reproduced by nothing, and no tier reopens a session over a used page.

The model is origin/main's again. The last round split its session end into
`ended` and `reopen` and gave `hold_empty` two parameters so that the page
kept a dead session's cursors. The search does not visit the difference: a
state is keyed by its queues, and nothing looks at the page between `notice`
and `reconnect`. With and without the split, `cargo test -p toyos-blockring
--lib -- --nocapture --test-threads 1` prints the same eighteen verdict lines
(end states and flushes given up, per bound). The one test that wanted a used
page walks to `crash`, where main's model already holds one, and ends the
clone's session itself.

That test is named for what it can fail on:
`a_used_page_reads_fresh_once_a_servers_ends_are_made_over_it`. Its old name
stated blockd's order, which it stays green without.

The compile-fail case on `Opened` names no error code. rustdoc reads one only
on a nightly build and the host suites run a stable one, so `E0451` was read
by nothing: `E0308` in its place left the doc-tests green. The block beside
it that compiles is what holds the case, as in `toyos-net-wire`. Filed as
`a-compile-fail-case-names-an-error-code-rustdoc-never-reads`, with the same
mutation run on `toyos_bootmap::DirectMapEnd` (E0603) and
`toyos_transport::Place` (E0080): both doc-test runs exit 0 under E0308.

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

Japabu commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

Controls and mutations at 0d2642da2 (origin/main 46af79d5d merged in at c0c12408a). Each patch was checked with git apply --check, applied, built and run, and reversed by the script at the end, which also shows the tree clean after each; the exits are the commands' own. They replace the ones posted for 7a8c3b21e: m1 went with the model scaffolding it mutated, and m4 with the error code it changed. The same script at 9a2ea85e9, one issue file earlier, gave the same exits.

Exits

head 0d2642da252b6853a1c48f38fbb0ce2900537c3a
origin/main 46af79d5d1e61a8d9566e7f3297c34ab949b96f0
green-blockring: EXIT=0 (cargo test -p toyos-blockring)
green-blockd: EXIT=0 (cargo test --manifest-path userland/blockd/Cargo.toml --target aarch64-apple-darwin)
green-transport: EXIT=0 (cargo test -p toyos-transport)
armB: blockd's two files and layout.rs equal origin/main's: EXIT=0
armB-doc: EXIT=101 (cargo test -p toyos-blockring --doc)
armB-lib: EXIT=0 (cargo test -p toyos-blockring --lib)
armB-blockd: EXIT=0 (cargo test --manifest-path userland/blockd/Cargo.toml --target aarch64-apple-darwin)
armC-blockd: EXIT=101 (cargo test --manifest-path userland/blockd/Cargo.toml --target aarch64-apple-darwin)
m2-blockring: EXIT=101 (cargo test -p toyos-blockring --lib)
m2-transport: EXIT=0 (cargo test -p toyos-transport)
m3-blockring: EXIT=101 (cargo test -p toyos-blockring --lib)
m3-transport: EXIT=0 (cargo test -p toyos-transport)
m5-bootmap: EXIT=0 (cargo test -p toyos-bootmap --doc)
m6-transport: EXIT=0 (cargo test -p toyos-transport --doc)
m7-one-field-public: EXIT=0 (cargo test -p toyos-blockring --doc)
m8-both-fields-public: EXIT=101 (cargo test -p toyos-blockring --doc)
status after: []
head after 0d2642da252b6853a1c48f38fbb0ce2900537c3a
ARMS-DONE

Arm B: the whole fix reverted onto origin/main's production files, the tests kept

userland/blockd/src/main.rs, session.rs and toyos-blockring/src/layout.rs equal origin/main's under it (git diff --quiet, EXIT=0); wire.rs is origin/main's plus the fifteen doc lines of the two doc-tests.

diff --git a/toyos-blockring/src/layout.rs b/toyos-blockring/src/layout.rs
index 56f65d701..05f60c90d 100644
--- a/toyos-blockring/src/layout.rs
+++ b/toyos-blockring/src/layout.rs
@@ -62,8 +62,7 @@ pub fn client<W: Word>(page: &[W; RING_WORDS]) -> ClientRings {
 
 /// The server's ends of a session page it was sent, every word it owns set to
 /// 0. Whatever the client left in its own is bounded when first looked at.
-/// A server's way to them is [`crate::wire::Opened::over`].
-pub(crate) fn server<W: Word>(page: &[W; RING_WORDS]) -> ServerRings {
+pub fn server<W: Word>(page: &[W; RING_WORDS]) -> ServerRings {
     (Consumer::new(page, REQUESTS), Producer::new(page, COMPLETIONS))
 }
 
diff --git a/toyos-blockring/src/wire.rs b/toyos-blockring/src/wire.rs
index 71236c917..b33011e3a 100644
--- a/toyos-blockring/src/wire.rs
+++ b/toyos-blockring/src/wire.rs
@@ -5,16 +5,6 @@
 //! with it, and is answered [`MSG_OPENED`] or [`MSG_REFUSED`] with a
 //! [`Refusal`]. After `MSG_OPENED` the connection carries nothing but doorbell
 //! bytes, each way.
-//!
-//! **The answer comes with the server's ends of the page.** A client looks at
-//! the page the moment it hears, and one that opens the same region again
-//! sends the cursors its last server left on it. So [`Opened::over`] makes a
-//! server's ends and its answer together: nothing else makes the ends, and an
-//! answer is otherwise only read off the wire ([`Opened::decode`]).
-
-use toyos_transport::Word;
-
-use crate::layout::{self, ServerRings, RING_WORDS};
 
 /// Open the partition whose unique GUID is the payload, over the region sent
 /// with it. Handles: the region.
@@ -51,28 +41,13 @@ pub const GUID_BYTES: usize = 16;
 /// ```
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub struct Opened {
-    blocks: u64,
-    unique: [u8; GUID_BYTES],
+    pub blocks: u64,
+    pub unique: [u8; GUID_BYTES],
 }
 
 impl Opened {
     pub const BYTES: usize = 8 + GUID_BYTES;
 
-    /// A server's ends of the session `page` it was sent, every word it owns
-    /// set to 0, and the answer to send after: `blocks` of the partition
-    /// `unique`.
-    pub fn over<W: Word>(page: &[W; RING_WORDS], blocks: u64, unique: [u8; GUID_BYTES]) -> (ServerRings, Self) {
-        (layout::server(page), Self { blocks, unique })
-    }
-
-    pub fn blocks(&self) -> u64 {
-        self.blocks
-    }
-
-    pub fn unique(&self) -> [u8; GUID_BYTES] {
-        self.unique
-    }
-
     pub fn encode(&self) -> [u8; Self::BYTES] {
         let mut out = [0u8; Self::BYTES];
         out[..8].copy_from_slice(&self.blocks.to_le_bytes());
diff --git a/userland/blockd/src/main.rs b/userland/blockd/src/main.rs
index b98b9a6cb..09a3aa282 100644
--- a/userland/blockd/src/main.rs
+++ b/userland/blockd/src/main.rs
@@ -55,7 +55,7 @@ use toyos_abi::part::{PartGuid, GUID_TEXT_LEN};
 use toyos_abi::syscall::{DEV_PREFIX, SyscallError};
 use toyos_blockhold::Holds;
 use toyos_blockring::entry::{Completion, Op};
-use toyos_blockring::layout::{ServerRings, DEPTH};
+use toyos_blockring::layout::{self, ServerRings, DEPTH};
 use toyos_blockring::server::{ServerSession, Taken};
 use toyos_blockring::wire::{self, Opened, Refusal};
 use toyos_blockring::{BLOCK_BYTES, PORT, SESSION_BYTES};
@@ -247,10 +247,10 @@ struct Service {
 /// A session decided on and not yet told to its client.
 struct Opening {
     region: Region,
-    rings: ServerRings,
-    opened: Opened,
     device_addr: u64,
     first: u64,
+    blocks: u64,
+    unique: [u8; 16],
 }
 
 impl Service {
@@ -303,8 +303,7 @@ impl Service {
         let (first, blocks) = self.place(guid)?;
         match self.ctrl.up().claim().dma_map(region.handle()) {
             Ok(mapping) if mapping.bytes == SESSION_BYTES as u64 => {
-                let (rings, opened) = Opened::over(region.words(), blocks, guid);
-                Ok(Opening { region, rings, opened, device_addr: mapping.device_addr, first })
+                Ok(Opening { region, device_addr: mapping.device_addr, first, blocks, unique: guid })
             }
             // A region longer than a session would spend the claim's bound on
             // the kernel's side for every other client: refused whole.
@@ -329,15 +328,16 @@ impl Service {
     fn admit(&mut self, opening: Opening, conn: Connection) -> u64 {
         let id = self.next_id;
         self.next_id += 1;
+        let rings = layout::server(opening.region.words());
         self.sessions.insert(
             id,
             Served {
                 conn,
                 region: opening.region,
                 device_addr: opening.device_addr,
-                rings: opening.rings,
-                state: ServerSession::new(opening.first, opening.opened.blocks()),
-                unique: opening.opened.unique(),
+                rings,
+                state: ServerSession::new(opening.first, opening.blocks),
+                unique: opening.unique,
                 closing: false,
                 requests: 0,
                 posted: false,
@@ -714,13 +714,13 @@ fn handshake(service: &mut Service, p: Pending, msg_type: u32, payload_len: usiz
             refuse(&p.conn, why);
         }
         Ok(opening) => {
-            let opened = opening.opened;
+            let opened = Opened { blocks: opening.blocks, unique: guid };
             if p.conn.try_send_bytes(wire::MSG_OPENED, &opened.encode()).is_err() {
                 service.abandon(opening);
                 return;
             }
             let id = service.admit(opening, p.conn);
-            println!("blockd: session {id} opened {} ({} blocks)", guid_text(guid), opened.blocks());
+            println!("blockd: session {id} opened {} ({} blocks)", guid_text(guid), opened.blocks);
         }
     }
 }
diff --git a/userland/blockd/src/session.rs b/userland/blockd/src/session.rs
index 8de1de510..e00613a55 100644
--- a/userland/blockd/src/session.rs
+++ b/userland/blockd/src/session.rs
@@ -159,7 +159,7 @@ impl Session {
 
     /// The partition's length in blocks.
     pub fn blocks(&self) -> u64 {
-        self.opened.blocks()
+        self.opened.blocks
     }
 
     /// The most requests this session has had on the wire at once.

armB-doc.log (EXIT=101):

   Compiling toyos-blockring v0.1.0 (/Users/jan/Dev/jan/toyos-blockdreplay/toyos-blockring)
    Finished `test` profile [optimized + debuginfo] target(s) in 0.19s
   Doc-tests toyos_blockring

running 2 tests
test toyos-blockring/src/wire.rs - wire::Opened (line 30) ... FAILED
test toyos-blockring/src/wire.rs - wire::Opened (line 39) - compile fail ... FAILED

failures:

---- toyos-blockring/src/wire.rs - wire::Opened (line 30) stdout ----
error[E0599]: no associated function or constant named `over` found for struct `Opened` in the current scope
  --> toyos-blockring/src/wire.rs:35:32
   |
35 | let (_ends, _answer) = Opened::over(&page, 1, [0; 16]);
   |                                ^^^^ associated function or constant not found in `Opened`
   |
note: if you're trying to build a new `Opened`, consider using `Opened::decode` which returns `Option<Opened>`
  --> toyos-blockring/src/wire.rs:58:5
   |
58 |     pub fn decode(bytes: &[u8]) -> Option<Self> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0599`.
Couldn't compile the test.
---- toyos-blockring/src/wire.rs - wire::Opened (line 39) stdout ----
Test compiled successfully, but it's marked `compile_fail`.

failures:
    toyos-blockring/src/wire.rs - wire::Opened (line 30)
    toyos-blockring/src/wire.rs - wire::Opened (line 39)

test result: FAILED. 0 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s

error: doctest failed, to rerun pass `-p toyos-blockring --doc`

Arm C: blockd's main.rs as on origin/main, against the crate at the head

diff --git a/userland/blockd/src/main.rs b/userland/blockd/src/main.rs
index b98b9a6cb..09a3aa282 100644
--- a/userland/blockd/src/main.rs
+++ b/userland/blockd/src/main.rs
@@ -55,7 +55,7 @@ use toyos_abi::part::{PartGuid, GUID_TEXT_LEN};
 use toyos_abi::syscall::{DEV_PREFIX, SyscallError};
 use toyos_blockhold::Holds;
 use toyos_blockring::entry::{Completion, Op};
-use toyos_blockring::layout::{ServerRings, DEPTH};
+use toyos_blockring::layout::{self, ServerRings, DEPTH};
 use toyos_blockring::server::{ServerSession, Taken};
 use toyos_blockring::wire::{self, Opened, Refusal};
 use toyos_blockring::{BLOCK_BYTES, PORT, SESSION_BYTES};
@@ -247,10 +247,10 @@ struct Service {
 /// A session decided on and not yet told to its client.
 struct Opening {
     region: Region,
-    rings: ServerRings,
-    opened: Opened,
     device_addr: u64,
     first: u64,
+    blocks: u64,
+    unique: [u8; 16],
 }
 
 impl Service {
@@ -303,8 +303,7 @@ impl Service {
         let (first, blocks) = self.place(guid)?;
         match self.ctrl.up().claim().dma_map(region.handle()) {
             Ok(mapping) if mapping.bytes == SESSION_BYTES as u64 => {
-                let (rings, opened) = Opened::over(region.words(), blocks, guid);
-                Ok(Opening { region, rings, opened, device_addr: mapping.device_addr, first })
+                Ok(Opening { region, device_addr: mapping.device_addr, first, blocks, unique: guid })
             }
             // A region longer than a session would spend the claim's bound on
             // the kernel's side for every other client: refused whole.
@@ -329,15 +328,16 @@ impl Service {
     fn admit(&mut self, opening: Opening, conn: Connection) -> u64 {
         let id = self.next_id;
         self.next_id += 1;
+        let rings = layout::server(opening.region.words());
         self.sessions.insert(
             id,
             Served {
                 conn,
                 region: opening.region,
                 device_addr: opening.device_addr,
-                rings: opening.rings,
-                state: ServerSession::new(opening.first, opening.opened.blocks()),
-                unique: opening.opened.unique(),
+                rings,
+                state: ServerSession::new(opening.first, opening.blocks),
+                unique: opening.unique,
                 closing: false,
                 requests: 0,
                 posted: false,
@@ -714,13 +714,13 @@ fn handshake(service: &mut Service, p: Pending, msg_type: u32, payload_len: usiz
             refuse(&p.conn, why);
         }
         Ok(opening) => {
-            let opened = opening.opened;
+            let opened = Opened { blocks: opening.blocks, unique: guid };
             if p.conn.try_send_bytes(wire::MSG_OPENED, &opened.encode()).is_err() {
                 service.abandon(opening);
                 return;
             }
             let id = service.admit(opening, p.conn);
-            println!("blockd: session {id} opened {} ({} blocks)", guid_text(guid), opened.blocks());
+            println!("blockd: session {id} opened {} ({} blocks)", guid_text(guid), opened.blocks);
         }
     }
 }

armC-blockd.log (EXIT=101):

   Compiling toyos-blockring v0.1.0 (/Users/jan/Dev/jan/toyos-blockdreplay/toyos-blockring)
   Compiling blockd v0.0.0 (/Users/jan/Dev/jan/toyos-blockdreplay/userland/blockd)
error[E0603]: function `server` is private
   --> blockd/src/main.rs:331:29
    |
331 |         let rings = layout::server(opening.region.words());
    |                             ^^^^^^ private function
    |
note: the function `server` is defined here
   --> /Users/jan/Dev/jan/toyos-blockdreplay/toyos-blockring/src/layout.rs:66:1
    |
 66 | pub(crate) fn server<W: Word>(page: &[W; RING_WORDS]) -> ServerRings {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0616]: field `blocks` of struct `toyos_blockring::wire::Opened` is private
   --> blockd/src/main.rs:723:92
    |
723 |             println!("blockd: session {id} opened {} ({} blocks)", guid_text(guid), opened.blocks);
    |                                                                                            ^^^^^^ private field
    |
help: a method `blocks` also exists, call it with parentheses
    |
723 |             println!("blockd: session {id} opened {} ({} blocks)", guid_text(guid), opened.blocks());
    |                                                                                                  ++

Some errors have detailed explanations: E0603, E0616.
For more information about an error, try `rustc --explain E0603`.
error: could not compile `blockd` (bin "blockd" test) due to 2 previous errors

m2: Consumer::new stores no head

--- a/toyos-transport/src/queue.rs
+++ b/toyos-transport/src/queue.rs
@@ -155,7 +155,7 @@ pub struct Consumer<const E: usize, const D: u32, const N: usize> {
 impl<const E: usize, const D: u32, const N: usize> Consumer<E, D, N> {
     /// This end of the queue at `place`, its head stored 0.
     pub fn new<W: Word>(page: &[W; N], place: Place<E, D, N>) -> Self {
-        word(page, place.head).store(0, Ordering::Release);
+        let _ = word(page, place.head);
         Self { place, local: 0, released: 0, ready: 0 }
     }
 

m2-blockring.log (EXIT=101; cargo test -p toyos-transport under the same patch: EXIT=0):

   Compiling toyos-transport v0.1.0 (/Users/jan/Dev/jan/toyos-blockdreplay/toyos-transport)
   Compiling toyos-blockring v0.1.0 (/Users/jan/Dev/jan/toyos-blockdreplay/toyos-blockring)
    Finished `test` profile [optimized + debuginfo] target(s) in 0.57s
     Running unittests src/lib.rs (target/debug/deps/toyos_blockring-d23ed736a519e670)

running 20 tests
test entry::tests::a_request_outside_its_bounds_is_refused_by_tag ... ok
test entry::tests::a_completion_survives_its_words_and_refuses_what_it_does_not_define ... ok
test entry::tests::a_request_survives_its_words ... ok
test client::tests::a_second_answer_for_one_tag_is_a_violation ... ok
test client::tests::nothing_goes_out_before_a_session ... ok
test client::tests::a_write_given_up_poisons_every_later_flush ... ok
test client::tests::what_was_on_the_wire_at_the_end_is_refused_and_what_was_not_waits ... ok
test client::tests::a_lost_flush_reissues_then_asks_again ... ok
test client::tests::overlapping_writes_go_out_again_in_order ... ok
test server::tests::a_flush_after_a_loss_answers_lost_once ... ok
test server::tests::a_reset_answers_in_the_order_taken ... ok
test server::tests::a_reset_answers_once_and_the_late_device_answer_is_dropped ... ok
test server::tests::a_tag_in_flight_twice_is_refused_unissued ... ok
test wire::tests::opened_and_refusal_survive_their_bytes_and_nothing_else_decodes ... ok
test model::tests::a_used_page_reads_fresh_once_a_servers_ends_are_made_over_it ... FAILED
test model::tests::states_named_alike_act_alike ... ok
test model::tests::the_model_reaches_the_end_it_should ... ok
test model::tests::the_model_reaches_a_write_given_up ... ok
test model::tests::what_a_flush_calls_durable_is_on_the_medium ... ok
test model::tests::every_request_is_answered_exactly_once ... ok

failures:

---- model::tests::a_used_page_reads_fresh_once_a_servers_ends_are_made_over_it stdout ----

thread 'model::tests::a_used_page_reads_fresh_once_a_servers_ends_are_made_over_it' (76249107) panicked at toyos-blockring/src/model.rs:791:9:
assertion `left == right` failed
  left: Err(HeadPastTail)
 right: Ok(4)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    model::tests::a_used_page_reads_fresh_once_a_servers_ends_are_made_over_it

test result: FAILED. 19 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.90s

error: test failed, to rerun pass `-p toyos-blockring --lib`

m3: Producer::new stores no tail

--- a/toyos-transport/src/queue.rs
+++ b/toyos-transport/src/queue.rs
@@ -105,7 +105,7 @@ pub struct Producer<const E: usize, const D: u32, const N: usize> {
 impl<const E: usize, const D: u32, const N: usize> Producer<E, D, N> {
     /// This end of the queue at `place`, its tail stored 0.
     pub fn new<W: Word>(page: &[W; N], place: Place<E, D, N>) -> Self {
-        word(page, place.tail).store(0, Ordering::Release);
+        let _ = word(page, place.tail);
         Self { place, local: 0, published: 0, room: D }
     }
 

m3-blockring.log (EXIT=101; cargo test -p toyos-transport under the same patch: EXIT=0):

   Compiling toyos-transport v0.1.0 (/Users/jan/Dev/jan/toyos-blockdreplay/toyos-transport)
   Compiling toyos-blockring v0.1.0 (/Users/jan/Dev/jan/toyos-blockdreplay/toyos-blockring)
    Finished `test` profile [optimized + debuginfo] target(s) in 0.55s
     Running unittests src/lib.rs (target/debug/deps/toyos_blockring-d23ed736a519e670)

running 20 tests
test client::tests::a_second_answer_for_one_tag_is_a_violation ... ok
test client::tests::nothing_goes_out_before_a_session ... ok
test client::tests::a_lost_flush_reissues_then_asks_again ... ok
test client::tests::a_write_given_up_poisons_every_later_flush ... ok
test entry::tests::a_completion_survives_its_words_and_refuses_what_it_does_not_define ... ok
test client::tests::overlapping_writes_go_out_again_in_order ... ok
test entry::tests::a_request_outside_its_bounds_is_refused_by_tag ... ok
test client::tests::what_was_on_the_wire_at_the_end_is_refused_and_what_was_not_waits ... ok
test entry::tests::a_request_survives_its_words ... ok
test model::tests::a_used_page_reads_fresh_once_a_servers_ends_are_made_over_it ... FAILED
test model::tests::states_named_alike_act_alike ... ok
test model::tests::the_model_reaches_a_write_given_up ... FAILED
test server::tests::a_flush_after_a_loss_answers_lost_once ... ok
test server::tests::a_reset_answers_in_the_order_taken ... ok
test server::tests::a_reset_answers_once_and_the_late_device_answer_is_dropped ... ok
test server::tests::a_tag_in_flight_twice_is_refused_unissued ... ok
test wire::tests::opened_and_refusal_survive_their_bytes_and_nothing_else_decodes ... ok
test model::tests::the_model_reaches_the_end_it_should ... ok
test model::tests::every_request_is_answered_exactly_once ... FAILED
test model::tests::what_a_flush_calls_durable_is_on_the_medium ... FAILED

failures:

---- model::tests::a_used_page_reads_fresh_once_a_servers_ends_are_made_over_it stdout ----

thread 'model::tests::a_used_page_reads_fresh_once_a_servers_ends_are_made_over_it' (76250112) panicked at toyos-blockring/src/model.rs:792:9:
assertion `left == right` failed
  left: Ok(Some([Untrusted(4), Untrusted(0), Untrusted(0), Untrusted(0)]))
 right: Ok(None)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

---- model::tests::the_model_reaches_a_write_given_up stdout ----

thread 'model::tests::the_model_reaches_a_write_given_up' (76250115) panicked at toyos-blockring/src/model.rs:184:13:
assertion `left == right` failed: the request ring gave what the queue does not hold
  left: Ok(Some([Untrusted(2), Untrusted(4), Untrusted(0), Untrusted(0), Untrusted(1), Untrusted(0), Untrusted(0), Untrusted(0)]))
 right: Ok(None)

---- model::tests::every_request_is_answered_exactly_once stdout ----
Failures { resets: 0, crashes: 0, errors: 0 }: 28 end states, 0 with a flush given up, None
Failures { resets: 1, crashes: 0, errors: 0 }: 492 end states, 0 with a flush given up, None

thread 'model::tests::every_request_is_answered_exactly_once' (76250113) panicked at toyos-blockring/src/model.rs:184:13:
assertion `left == right` failed: the request ring gave what the queue does not hold
  left: Err(TailPastDepth)
 right: Ok(None)

---- model::tests::what_a_flush_calls_durable_is_on_the_medium stdout ----
Failures { resets: 0, crashes: 0, errors: 0 }: 28 end states, 0 with a flush given up, None
Failures { resets: 1, crashes: 0, errors: 0 }: 492 end states, 0 with a flush given up, None

thread 'model::tests::what_a_flush_calls_durable_is_on_the_medium' (76250117) panicked at toyos-blockring/src/model.rs:184:13:
assertion `left == right` failed: the request ring gave what the queue does not hold
  left: Err(TailPastDepth)
 right: Ok(None)


failures:
    model::tests::a_used_page_reads_fresh_once_a_servers_ends_are_made_over_it
    model::tests::every_request_is_answered_exactly_once
    model::tests::the_model_reaches_a_write_given_up
    model::tests::what_a_flush_calls_durable_is_on_the_medium

test result: FAILED. 16 passed; 4 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.42s

error: test failed, to rerun pass `-p toyos-blockring --lib`

m7 and m8: Opened's fields made public, one and then both

diff --git a/toyos-blockring/src/wire.rs b/toyos-blockring/src/wire.rs
index 71236c917..afcc8288e 100644
--- a/toyos-blockring/src/wire.rs
+++ b/toyos-blockring/src/wire.rs
@@ -51,7 +51,7 @@ pub const GUID_BYTES: usize = 16;
 /// ```
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub struct Opened {
-    blocks: u64,
+    pub blocks: u64,
     unique: [u8; GUID_BYTES],
 }
 

m7-one-field-public.log (EXIT=0: the literal is still refused):

   Compiling toyos-transport v0.1.0 (/Users/jan/Dev/jan/toyos-blockdreplay/toyos-transport)
   Compiling toyos-blockring v0.1.0 (/Users/jan/Dev/jan/toyos-blockdreplay/toyos-blockring)
    Finished `test` profile [optimized + debuginfo] target(s) in 0.17s
   Doc-tests toyos_blockring

running 2 tests
test toyos-blockring/src/wire.rs - wire::Opened (line 49) - compile fail ... ok
test toyos-blockring/src/wire.rs - wire::Opened (line 40) ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.25s

diff --git a/toyos-blockring/src/wire.rs b/toyos-blockring/src/wire.rs
index 71236c917..5d305aba4 100644
--- a/toyos-blockring/src/wire.rs
+++ b/toyos-blockring/src/wire.rs
@@ -51,8 +51,8 @@ pub const GUID_BYTES: usize = 16;
 /// ```
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub struct Opened {
-    blocks: u64,
-    unique: [u8; GUID_BYTES],
+    pub blocks: u64,
+    pub unique: [u8; GUID_BYTES],
 }
 
 impl Opened {

m8-both-fields-public.log (EXIT=101, on the compile-fail case alone):

   Compiling toyos-blockring v0.1.0 (/Users/jan/Dev/jan/toyos-blockdreplay/toyos-blockring)
    Finished `test` profile [optimized + debuginfo] target(s) in 0.11s
   Doc-tests toyos_blockring

running 2 tests
test toyos-blockring/src/wire.rs - wire::Opened (line 49) - compile fail ... FAILED
test toyos-blockring/src/wire.rs - wire::Opened (line 40) ... ok

failures:

---- toyos-blockring/src/wire.rs - wire::Opened (line 49) stdout ----
Test compiled successfully, but it's marked `compile_fail`.

failures:
    toyos-blockring/src/wire.rs - wire::Opened (line 49)

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s

error: doctest failed, to rerun pass `-p toyos-blockring --doc`

m5 and m6: a compile-fail case's error code changed to E0308, in the two other crates that name one

Both stay green (EXIT=0): the evidence of issues/build/a-compile-fail-case-names-an-error-code-rustdoc-never-reads.md.

diff --git a/toyos-bootmap/src/lib.rs b/toyos-bootmap/src/lib.rs
index 92f7237bf..4d4c90d3d 100644
--- a/toyos-bootmap/src/lib.rs
+++ b/toyos-bootmap/src/lib.rs
@@ -61,7 +61,7 @@ pub const DIRECT_MAP_WINDOW: u64 = (512 - ROOT_HIGH_HALF as u64) * GIB_PER_PDPT
 
 /// One past the kernel direct map's last byte.
 ///
-/// ```compile_fail,E0603
+/// ```compile_fail,E0308
 /// let _ = toyos_bootmap::DirectMapEnd(1 << 52);
 /// ```
 #[derive(Clone, Copy)]
diff --git a/toyos-transport/src/queue.rs b/toyos-transport/src/queue.rs
index 681b101b2..0f16e404c 100644
--- a/toyos-transport/src/queue.rs
+++ b/toyos-transport/src/queue.rs
@@ -40,17 +40,17 @@ fn clamp(claimed: Untrusted<u32>, cap: u32, broken: Violation) -> Result<u32, Vi
 ///
 /// A place with a word at `N` or past it does not compile:
 ///
-/// ```compile_fail,E0080
+/// ```compile_fail,E0308
 /// use toyos_transport::Place;
 /// const QUEUE: Place<2, 4, 10> = Place::new::<0, 1, 3>();
 /// ```
 ///
-/// ```compile_fail,E0080
+/// ```compile_fail,E0308
 /// use toyos_transport::Place;
 /// const QUEUE: Place<2, 4, 10> = Place::new::<10, 1, 2>();
 /// ```
 ///
-/// ```compile_fail,E0080
+/// ```compile_fail,E0308
 /// use toyos_transport::Place;
 /// const QUEUE: Place<2, 4, 10> = Place::new::<0, 10, 2>();
 /// ```
   Compiling toyos-bootmap v0.1.0 (/Users/jan/Dev/jan/toyos-blockdreplay/toyos-bootmap)
    Finished `test` profile [optimized + debuginfo] target(s) in 0.10s
   Doc-tests toyos_bootmap

running 1 test
test toyos-bootmap/src/lib.rs - DirectMapEnd (line 64) - compile fail ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s

   Compiling toyos-transport v0.1.0 (/Users/jan/Dev/jan/toyos-blockdreplay/toyos-transport)
    Finished `test` profile [optimized + debuginfo] target(s) in 0.08s
   Doc-tests toyos_transport

running 4 tests
test toyos-transport/src/queue.rs - queue::Place (line 53) - compile fail ... ok
test toyos-transport/src/queue.rs - queue::Place (line 48) - compile fail ... ok
test toyos-transport/src/queue.rs - queue::Place (line 43) - compile fail ... ok
test toyos-transport/src/queue.rs - queue::Place (line 36) ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.16s

The model with and without the last round's split

Measured on the tree that became 9a2ea85e9, whose model.rs is this head's. The patch below is that model.rs against 7a8c3b21e's: reversed, it puts the ended/reopen split and hold_empty's two parameters back. cargo test -p toyos-blockring --lib -- --nocapture --test-threads 1 exits 0 both ways, and diff of the eighteen end states lines the two runs print exits 0.

diff --git a/toyos-blockring/src/model.rs b/toyos-blockring/src/model.rs
index 67444d311..9a103bc0e 100644
--- a/toyos-blockring/src/model.rs
+++ b/toyos-blockring/src/model.rs
@@ -129,32 +129,21 @@ struct Queues {
 impl Queues {
     fn new() -> Self {
         let page = core::array::from_fn(|_| Shared(Cell::new(0)));
-        let (client, server) = (Self::client_ends(&page), Self::server_ends(&page));
+        let (client, server) = Self::ends(&page);
         Self { sq: VecDeque::new(), cq: VecDeque::new(), page, client, server }
     }
 
-    /// The client's ends over `page`, its two cursors stored 0.
-    fn client_ends(page: &[Shared; WORDS]) -> ClientEnds {
-        (Producer::new(page, SQ), Consumer::new(page, CQ))
+    /// Both ends over `page`, every cursor stored 0.
+    fn ends(page: &[Shared; WORDS]) -> (ClientEnds, ServerEnds) {
+        ((Producer::new(page, SQ), Consumer::new(page, CQ)), (Consumer::new(page, SQ), Producer::new(page, CQ)))
     }
 
-    /// A server's ends over `page`, its two cursors stored 0.
-    fn server_ends(page: &[Shared; WORDS]) -> ServerEnds {
-        (Consumer::new(page, SQ), Producer::new(page, CQ))
-    }
-
-    /// The session is over: what either queue held is gone, and the page keeps
-    /// every cursor where its end left it.
-    fn ended(&mut self) {
+    /// The session is over: what either queue held is gone, and the next
+    /// session's two ends start over the same page.
+    fn reset(&mut self) {
         self.sq.clear();
         self.cq.clear();
-    }
-
-    /// The next session over the same page: both ends set their cursors
-    /// before the client looks at it ([`crate::wire::Opened::over`]).
-    fn reopen(&mut self) {
-        self.client = Self::client_ends(&self.page);
-        self.server = Self::server_ends(&self.page);
+        (self.client, self.server) = Self::ends(&self.page);
     }
 
     fn send(&mut self, request: Request) {
@@ -189,13 +178,12 @@ impl Queues {
         Some(want)
     }
 
-    /// A ring whose queue is empty gives nothing to an end that is looking:
-    /// the server while it lives, the client while its session is up.
-    fn hold_empty(&mut self, server: bool, client: bool) {
-        if server && self.sq.is_empty() {
+    /// A ring whose queue is empty gives nothing.
+    fn hold_empty(&mut self) {
+        if self.sq.is_empty() {
             assert_eq!(self.server.0.pop(&self.page), Ok(None), "the request ring gave what the queue does not hold");
         }
-        if client && self.cq.is_empty() {
+        if self.cq.is_empty() {
             assert_eq!(self.client.1.pop(&self.page), Ok(None), "the completion ring gave what the queue does not hold");
         }
     }
@@ -283,7 +271,6 @@ fn start(failures: Failures) -> World {
 }
 
 fn connect(world: &mut World) {
-    world.queues.reopen();
     let mut holds = Holds::new();
     holds.hold(0, BLOCKS as u64, 1).expect("a fresh server holds nothing");
     world.server = Some(Server { session: ServerSession::new(0, BLOCKS as u64), holds });
@@ -475,7 +462,7 @@ fn key(world: &World) -> String {
 }
 
 fn dfs(run: &mut Run, mut world: World) {
-    world.queues.hold_empty(world.alive, world.client.up());
+    world.queues.hold_empty();
     if run.broken.is_some() || !run.seen.insert(key(&world)) {
         return;
     }
@@ -646,7 +633,7 @@ fn next(script: &[Step], world: &World) -> Vec<(String, After)> {
     if !world.alive && world.client.up() {
         let mut w = world.clone();
         w.client.session_ended();
-        w.queues.ended();
+        w.queues.reset();
         after("notice".into(), Ok(w));
     }
 
@@ -783,25 +770,24 @@ mod tests {
         world
     }
 
-    /// The page a client reconnects over holds the dead server's cursors until
-    /// the new server sets its own. A client looking before then has no room
-    /// to ask and meets the last session's completion, which it refuses as the
-    /// server breaking the protocol; once the server's ends are made, as
-    /// blockd makes them before it answers the open, the page is a fresh one.
+    /// A used page reads fresh once a server's ends are made over it. A
+    /// client's ends alone leave the dead server's two cursors on it: no room
+    /// to ask, and the last session's completion to hear, which the client
+    /// refuses as the server breaking the protocol.
     #[test]
-    fn the_new_server_sets_its_cursors_before_the_client_looks() {
-        let world = walk(start(at_most(0, 1, 0)), &["ask", "take", "done", "read", "crash", "notice"]);
+    fn a_used_page_reads_fresh_once_a_servers_ends_are_made_over_it() {
+        let world = walk(start(at_most(0, 1, 0)), &["ask", "take", "done", "read", "crash"]);
         let page = &world.queues.page;
         let mut client = world.client.clone();
+        client.session_ended();
         client.session_started();
-        let (mut asks, mut hears) = Queues::client_ends(page);
+        let (mut asks, mut hears): ClientEnds = (Producer::new(page, SQ), Consumer::new(page, CQ));
         assert_eq!(asks.space(page), Err(Violation::HeadPastTail));
         let stale = hears.pop(page).expect("a tail inside the ring").expect("the last session's completion");
         let stale = Completion::decode(stale).expect("a completion the last server wrote");
         assert_eq!(client.complete(stale), Err(Violation::Tag));
 
-        let (mut asks, mut hears) = Queues::client_ends(page);
-        let _server = Queues::server_ends(page);
+        let ((mut asks, mut hears), _server) = Queues::ends(page);
         assert_eq!(asks.space(page), Ok(DEPTH));
         assert_eq!(hears.pop(page), Ok(None));
     }
test model::tests::every_request_is_answered_exactly_once ... Failures { resets: 0, crashes: 0, errors: 0 }: 28 end states, 0 with a flush given up, None
Failures { resets: 1, crashes: 0, errors: 0 }: 492 end states, 0 with a flush given up, None
Failures { resets: 0, crashes: 1, errors: 0 }: 244 end states, 0 with a flush given up, None
Failures { resets: 0, crashes: 0, errors: 1 }: 236 end states, 0 with a flush given up, None
Failures { resets: 1, crashes: 1, errors: 0 }: 460 end states, 0 with a flush given up, None
Failures { resets: 2, crashes: 2, errors: 0 }: 363 end states, 0 with a flush given up, None
Failures { resets: 0, crashes: 1, errors: 5 }: 373 end states, 32 with a flush given up, None
Failures { resets: 1, crashes: 1, errors: 4 }: 1300 end states, 162 with a flush given up, None
test model::tests::the_model_reaches_a_write_given_up ... Failures { resets: 0, crashes: 1, errors: 5 }: 373 end states, 32 with a flush given up, None
test model::tests::the_model_reaches_the_end_it_should ... Failures { resets: 0, crashes: 0, errors: 0 }: 28 end states, 0 with a flush given up, None
test model::tests::what_a_flush_calls_durable_is_on_the_medium ... Failures { resets: 0, crashes: 0, errors: 0 }: 28 end states, 0 with a flush given up, None
Failures { resets: 1, crashes: 0, errors: 0 }: 492 end states, 0 with a flush given up, None
Failures { resets: 0, crashes: 1, errors: 0 }: 244 end states, 0 with a flush given up, None
Failures { resets: 0, crashes: 0, errors: 1 }: 236 end states, 0 with a flush given up, None
Failures { resets: 1, crashes: 1, errors: 0 }: 460 end states, 0 with a flush given up, None
Failures { resets: 2, crashes: 2, errors: 0 }: 363 end states, 0 with a flush given up, None
Failures { resets: 0, crashes: 1, errors: 5 }: 373 end states, 32 with a flush given up, None
Failures { resets: 1, crashes: 1, errors: 4 }: 1300 end states, 162 with a flush given up, None

The script

#!/bin/sh
# Every control and mutation of #643's second round, at the head named below:
# each patch checked, applied, built and run, its exit recorded, and reversed,
# with the tree shown clean of it after. The rust gitlink is left out of every
# status here: the worktree's toolchain checkout is the build's to move.
W=/Users/jan/Dev/jan/toyos-blockdreplay
D=/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/643-r2
HOST=aarch64-apple-darwin
cd $W || exit 2
OUT=$D/arms.exits
echo "head $(git rev-parse HEAD)" > $OUT
echo "origin/main $(git rev-parse origin/main)" >> $OUT

dirty() { git status --porcelain --ignore-submodules=all; }

arm() { # name patch command...
    name=$1; patch=$2; shift 2
    if [ -n "$(dirty)" ]; then echo "$name: TREE NOT CLEAN BEFORE" >> $OUT; exit 2; fi
    git apply --check $patch || { echo "$name: PATCH DOES NOT APPLY" >> $OUT; exit 2; }
    git apply $patch
    "$@" > $D/$name.log 2>&1
    echo "$name: EXIT=$? ($*)" >> $OUT
    git apply -R $patch
    if [ -n "$(dirty)" ]; then echo "$name: TREE NOT CLEAN AFTER" >> $OUT; exit 2; fi
}

# Green arm: the head as it stands.
cargo test -p toyos-blockring > $D/green-blockring.log 2>&1; echo "green-blockring: EXIT=$? (cargo test -p toyos-blockring)" >> $OUT
cargo test --manifest-path userland/blockd/Cargo.toml --target $HOST > $D/green-blockd.log 2>&1; echo "green-blockd: EXIT=$? (cargo test --manifest-path userland/blockd/Cargo.toml --target $HOST)" >> $OUT
cargo test -p toyos-transport > $D/green-transport.log 2>&1; echo "green-transport: EXIT=$? (cargo test -p toyos-transport)" >> $OUT

# Arm B: the whole fix reverted onto origin/main's production files, the tests kept.
B=$D/armB/fix-reverted-tests-kept.patch
git apply --check $B && git apply $B
git diff --quiet origin/main -- userland/blockd/src/main.rs userland/blockd/src/session.rs toyos-blockring/src/layout.rs
echo "armB: blockd's two files and layout.rs equal origin/main's: EXIT=$?" >> $OUT
git apply -R $B
arm armB-doc $B cargo test -p toyos-blockring --doc
arm armB-lib $B cargo test -p toyos-blockring --lib
arm armB-blockd $B cargo test --manifest-path userland/blockd/Cargo.toml --target $HOST

# Arm C: blockd's main.rs as on origin/main, against the crate at the head.
arm armC-blockd $D/armC/blockd-at-main.patch cargo test --manifest-path userland/blockd/Cargo.toml --target $HOST

# Mutations of the transport's two constructors, against the model test and
# against the transport's own suite.
arm m2-blockring $D/mut/m2-consumer-new-stores-no-head.patch cargo test -p toyos-blockring --lib
arm m2-transport $D/mut/m2-consumer-new-stores-no-head.patch cargo test -p toyos-transport
arm m3-blockring $D/mut/m3-producer-new-stores-no-tail.patch cargo test -p toyos-blockring --lib
arm m3-transport $D/mut/m3-producer-new-stores-no-tail.patch cargo test -p toyos-transport

# A compile-fail case's error code changed to one the block does not raise.
arm m5-bootmap $D/mut/m5-bootmap-wrong-error-code.patch cargo test -p toyos-bootmap --doc
arm m6-transport $D/mut/m6-transport-wrong-error-code.patch cargo test -p toyos-transport --doc

# `Opened`'s fields made public: one, then both.
arm m7-one-field-public $D/mut/m7-one-field-public.patch cargo test -p toyos-blockring --doc
arm m8-both-fields-public $D/mut/m8-both-fields-public.patch cargo test -p toyos-blockring --doc

echo "status after: [$(dirty)]" >> $OUT
echo "head after $(git rev-parse HEAD)" >> $OUT
echo ARMS-DONE >> $OUT

@Japabu

Japabu commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

A blockd session opening at 0d2642da2: cargo test --test toyos-build -- iommu_virtio_platform --nocapture, EXIT=0, test result: ok. 1 passed, 1 total (11.3s). Three boots; the lines below are that run's log, 749 lines, cut to what init, blockd and fsd said in each boot, with the image the first boot ran and the verdict. The same run at 9a2ea85e9, one issue file earlier, exited 0 with the same lines.

The image (bin/blockd, 982552 bytes, the size cargo run -- --build-only staged at this head):

running 1 tests, 12 wide

    Finished `toyos` profile [optimized + debuginfo] target(s) in 0.01s
    Finished `toyos` profile [optimized + debuginfo] target(s) in 0.01s
   Compiling init v0.1.0 (/Users/jan/Dev/jan/toyos-blockdreplay/userland/init)
    Finished `toyos` profile [optimized + debuginfo] target(s) in 0.32s
root: adding 'bin/blockd' (982552 bytes)
root: adding 'bin/fsd' (1849280 bytes)
root: adding 'bin/init' (1582656 bytes)
root: adding 'bin/logd' (1244880 bytes)
root: adding 'bin/netd' (1330096 bytes)
root: adding 'bin/test-runner' (918960 bytes)
root: adding 'etc/system.manifest' (445 bytes)

Boot 0, tests/netcase behind a unit: blockd holds its claim, fsd opens DATA through it.

[serial 0] {0.303 init} init: started blockd
[serial 0] {0.318 init} init: started fsd
[serial 0] {0.332 init} init: started fsd
[serial 0] {0.340 fsd} fsd: FAT32 mounted, 35651584 bytes, 512-byte sectors, 512-byte clusters
[serial 0] {0.340 fsd} fsd: Log serving /log — FAT32, 0 files open; cache 1 blocks (0 dirty), 2 of 3 reads hit
[serial 0] {0.341 init} init: started fsd
[serial 0] {0.352 fsd} fsd: FAT32 mounted, 35651584 bytes, 512-byte sectors, 512-byte clusters
[serial 0] {0.353 fsd} fsd: Boot serving /boot — FAT32 read-only, 0 files open; cache 1 blocks (0 dirty), 2 of 3 reads hit
[serial 0] {0.357 blockd} blockd: NVMe up: 4 I/O queues of 63 commands, volatile write cache present, so a flush issues Flush, 512-byte sectors, 262144 sectors
[serial 0] {0.367 blockd} blockd: partition 639A73DC-ED31-4B0D-8A47-8DE4144FCC48 at block 256, 32256 blocks
[serial 0] {0.375 blockd} blockd: session 0 opened 639A73DC-ED31-4B0D-8A47-8DE4144FCC48 (32256 blocks)
[serial 0] {0.381 fsd} fsd: block 0 designates this partition for ToyOS; formatting it
[serial 0] {0.383 fsd} fsd: Data serving /apps, /config, /home, /state — bcachefs, 0 names, 0 files open; cache 4 blocks (4 dirty), 6 of 8 reads hit
[serial 0] {0.406 init} init: started logd
[serial 0] {0.424 init} init: started netd
[serial 0] {0.438 netd} netd: this claim answers 4096 bytes of configuration space and refuses every access outside them
[serial 0] {0.439 netd} netd: VirtIO: PCI 00:03.0 features device=0x10330bf8024 negotiated=0x300000020 access_platform=y
[serial 0] {0.441 netd} netd: MAC 52:54:00:12:34:56
[serial 0] {0.443 init} init: started test-runner
[serial 0] {0.447 test-runner} ===READY===
[serial 0] {0.456 netd} netd: DHCP: lease 10.0.2.15/24 from 10.0.2.2, gateway 10.0.2.2, dns [10.0.2.3], 14 ms after netd came up
[serial 0] {0.456 netd} netd: ready, at most 103 piped connections (4 MiB each of 4040 MiB total)
[serial 0] {0.457 logd} logd: this boot's kernel log is /log/2026-10-02-092122.log (2026-10-02 09:21:22 UTC)
[qemu] Reached ===READY===
  [iommu] headless: 3 virtio function(s) behind a unit = true, the audio function 00:04.0 among them

Boot 1, the same config with no unit: blockd's claim is refused, and it prints no session line.

[serial 1] {0.274 init} init: blockd: pci:1b36:0010 is on this machine and could not be handed over; the kernel's `pcidev:` or `partclaim:` line says why
[serial 1] {0.290 init} init: started blockd
[serial 1] {0.294 blockd} blockd: NOT SERVING — pci:1b36:0010 is on this machine and the kernel refused this service its claim; every partition on it is refused
[serial 1] {0.302 init} init: started fsd
[serial 1] {0.314 init} init: started fsd
[serial 1] {0.317 fsd} fsd: the block service would not list its partitions (Refused(ClaimRefused)); DATA is absent this boot
[serial 1] {0.318 fsd} fsd: Data serving /apps, /config, /home, /state — absent: the block service would not list its partitions (Refused(ClaimRefused))
[serial 1] {0.330 fsd} fsd: FAT32 mounted, 35651584 bytes, 512-byte sectors, 512-byte clusters
[serial 1] {0.331 fsd} fsd: Log serving /log — FAT32, 0 files open; cache 1 blocks (0 dirty), 2 of 3 reads hit
[serial 1] {0.335 init} init: started fsd
[serial 1] {0.352 fsd} fsd: FAT32 mounted, 35651584 bytes, 512-byte sectors, 512-byte clusters
[serial 1] {0.353 fsd} fsd: Boot serving /boot — FAT32 read-only, 0 files open; cache 1 blocks (0 dirty), 2 of 3 reads hit
[serial 1] {0.355 tid=1 init} init: /home/toy could not be made, so this boot has no session home: permission denied
[serial 1] {0.359 init} init: logd: /state/logd could not be made: permission denied
[serial 1] {0.365 init} init: started logd
[serial 1] {0.375 init} init: netd: /state/netd could not be made: permission denied
[serial 1] {0.376 init} init: netd: pci:1af4:1041 is on this machine and could not be handed over; the kernel's `pcidev:` or `partclaim:` line says why
[serial 1] {0.381 init} init: started netd
[serial 1] {0.386 netd} netd: no NIC on this machine, exiting
[serial 1] {0.394 init} init: started test-runner
[serial 1] {0.395 test-runner} ===READY===
[serial 1] {0.400 logd} logd: this boot's kernel log is /log/2026-10-02-092124.log (2026-10-02 09:21:24 UTC)
[qemu] Reached ===READY===
  [iommu] headless-no-iommu: 2 virtio function(s) behind a unit = false, the audio function 00:04.0 among them; the NIC's claim refused for want of a domain

Boot 2, tests/testcases with virtio-no-access-platform, and the verdict:

[serial 2] {0.300 init} init: started blockd
[serial 2] {0.313 init} init: started fsd
[serial 2] {0.326 init} init: started fsd
[serial 2] {0.334 fsd} fsd: FAT32 mounted, 35651584 bytes, 512-byte sectors, 512-byte clusters
[serial 2] {0.335 fsd} fsd: Log serving /log — FAT32, 0 files open; cache 1 blocks (0 dirty), 2 of 3 reads hit
[serial 2] {0.335 init} init: started fsd
[serial 2] {0.348 fsd} fsd: FAT32 mounted, 35651584 bytes, 512-byte sectors, 512-byte clusters
[serial 2] {0.349 fsd} fsd: Boot serving /boot — FAT32 read-only, 0 files open; cache 1 blocks (0 dirty), 2 of 3 reads hit
[serial 2] {0.352 blockd} blockd: NVMe up: 4 I/O queues of 63 commands, volatile write cache present, so a flush issues Flush, 512-byte sectors, 262144 sectors
[serial 2] {0.363 blockd} blockd: partition D7068279-0CBD-4B63-A4B6-11293760D57B at block 256, 32256 blocks
[serial 2] {0.371 blockd} blockd: session 0 opened D7068279-0CBD-4B63-A4B6-11293760D57B (32256 blocks)
[serial 2] {0.377 fsd} fsd: block 0 designates this partition for ToyOS; formatting it
[serial 2] {0.379 fsd} fsd: Data serving /apps, /config, /home, /state — bcachefs, 0 names, 0 files open; cache 4 blocks (4 dirty), 6 of 8 reads hit
[serial 2] {0.402 init} init: started logd
[serial 2] {0.413 init} init: soundd: no hda-audio on this machine
[serial 2] {0.413 init} init: soundd: no virtio-sound on this machine
[serial 2] {0.419 init} init: started soundd
[serial 2] {0.432 soundd} soundd: no audio device, presenting a null sink (44100Hz 2ch, 128 frames/period, streams discarded)
[serial 2] {0.436 soundd} soundd: null sink idle
[serial 2] {0.438 init} init: started test-runner
[serial 2] {0.441 test-runner} ===READY===
[serial 2] {0.449 logd} logd: this boot's kernel log is /log/2026-10-02-092129.log (2026-10-02 09:21:29 UTC)
[qemu] Reached ===READY===
  [iommu] declined: [kernel 0.247 cpu0] virtio-sound: NOT INITIALISED — PCI 00:04.0 refused the feature set 0x100000000 the driver accepted, leaving DEVICE_STATUS=0x3 without FEATURES_OK
  PASS  iommu_virtio_platform  (8s)
  --- 3 guests, 1 of them not the shipping kernel, 2 kernel build(s): ["", "boot-actuators,test-actuators"]
  --- irq census: 1 guest(s) reported, 158 interrupt(s), 138 of them on cpu0 (87.3%); per guest cpu0's share is median 87.3% p90 87.3% max 87.3%
      timer            31 (19.6% of all), 58.1% of them on cpu0
      xhci            117 (74.1% of all), 100.0% of them on cpu0
      tlb              10 (6.3% of all), 30.0% of them on cpu0
      widest guest reported 2 cpu(s)

host: fastest boot 2430 ms against the reference 1320 ms — liveness ceilings paid at 1.84x width
host: 14 core(s); a guest wider than that waits vcpus/cores longer again
test result: ok. 1 passed, 1 total (11.3s)

Found while checking who else names toyos-blockring. 520c0d1 deleted
`tests/toyos-rust-tests/src/bin/blockd_io.rs` and left `blockd`,
`toyos-blockring` and `toyos-fat32` in that crate's manifest: `git grep` for
the three under `tests/toyos-rust-tests` finds `Cargo.toml` and `Cargo.lock`
alone. Filed, not fixed: it is off this branch's task.

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

Japabu commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2: review of 0d2642da2 against origin/main 46af79d5d (merged in at c0c12408a); last reviewed head 7a8c3b21e. Net (git diff --shortstat origin/main...0d2642da2): 9 files, +164 −43. Production +55 −14 (wire.rs, layout.rs, blockd's main.rs and session.rs), unchanged since round 1 but for the fence's error code, and accepted there. Tests +23 −1 (model.rs: one import, one test; the model is origin/main's). Issues +86 −28.

Earlier BLOCKERs

  • CLOSED — nothing ran the changed open path where anyone reads it. The body carries cargo test --test toyos-build -- iommu_virtio_platform --nocapture, EXIT=0 at 0d2642da2 (gates.exits: head 0d2642da2…, status before: [], guest-iommu-nocapture EXIT=0), with blockd: session 0 opened 639A73DC-… (32256 blocks) and fsd's Data serving /apps, /config, /home, /state — bcachefs after it, in boots 0 and 2, and boot 1's blockd: NOT SERVING with no session line. The image is the head's: --build-only before it compiled toyos-transport, toyos-blockring, blockd, init and fsd and staged bin/blockd at 982552 bytes, the size the guest's image adds. It is a first open over a fresh region and cannot tell the order; round 1 asked for the path, and the order stands on arm C: blockd's main.rs as on origin/main against the crate at the head, EXIT=101, E0603 at main.rs:331.
  • CLOSED — the reorder issue. issues/filesystem/blockd-survives-its-death-reds-on-a-replacement-that-reorders-acknowledged-writes.md is deleted at the head. Run here: git merge-base --is-ancestor 17bb26416 <h> exits 1 for 8c5be843, a55d62c6 and 59bd29ff2 and 0 for origin/main; git grep reorders-acknowledged-writes 0d2642da2 exits 1, and so does a grep for the bare title. Its rule is at its site (toyos-blockring/src/lib.rs:28, client.rs:32).

Gates at the head, read from their logs: cargo run -- --ci host EXIT=0, [ci] Host: 59 step(s), all green, with a_used_page_reads_fresh_once_a_servers_ends_are_made_over_it ... ok, both wire::Opened doc-tests and blockd's host test in it; cargo test --test toyos-build EXIT=0, 21 passed, 21 total; tree clean before and after. Arms and mutations match arms.exits and their logs: arm B --doc 101 and --lib 0, m2 red at model.rs:791 alone, m3 at model.rs:792 and three model runs, m7 0, m8 101, m5 and m6 0.

Weighed, nothing owed in the diff:

  • git submodule status in the worktree breaks root CLAUDE.md's rule and left no trace: .git/modules/rust/config was last written Oct 1 19:04, before the round; its core.worktree is ../../../rust, the primary's; git -C rust rev-parse --show-toplevel in the primary answers /Users/jan/Dev/jan/toyos/rust; the primary's rust reflog last moved 2026-09-27.
  • The three issue files hold against issues/README.md: frontmatter, tooling with open, slugs unique across areas, every citation a file. git grep -E 'toyos_fat32|toyos_blockring|blockd::' 0d2642da2 -- tests/toyos-rust-tests exits 1, and Stage D builds its replacements on the host, so the three dependencies are dead as filed. Stages C and H do exit on "a compile-fail case", and git grep compile_fail,E finds the four fences the issue's table names and no other.

BLOCKER

None.

NOTE

  • issues/build/the-guest-test-crate-depends-on-three-crates-no-test-uses.md:15 — no owner — a compromise the branch found is recorded with an owner, evidence and an exit; the other two files name theirs.
  • PR body, "Green arm" — "Under --nocapture the compile-fail case's error is E0451, …" is a measurement with no log in the body or either comment (grep E0451 over both finds nothing; it is in the round's try-blockring-new-model.log:51, taken before 9a2ea85e9 was committed). The sentence carries its log at this head or goes; m8 already ties the case to the private fields.

REMOVE

  • PR body, "The model is origin/main's.", the whole paragraph — a split made and unmade inside the branch; main's record has no model change to explain, and the measurement is in the controls comment.
  • PR body, "Two tests", second bullet — "and its name no longer says it does": a name main never had.

LAND AFTER NAMED CHANGES

The review of 0d2642d on #643 found the file with evidence and an exit
and no owner; the branch's other two issue files name theirs, and a
compromise is recorded with all three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
@Japabu
Japabu marked this pull request as ready for review October 2, 2026 09:41
@Japabu
Japabu added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 80bc97b Oct 2, 2026
2 checks passed
@Japabu
Japabu deleted the wt/toyos-blockdreplay branch October 2, 2026 09:48
Japabu added a commit that referenced this pull request Oct 2, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
Japabu added a commit that referenced this pull request Oct 2, 2026
Main gained #643, blockd setting its ring cursors before it answers an
open, with its issues. It shares no file with this branch and the merge
has no conflict. Main's `rust` gitlink is unchanged at 3f6050fc829, so
the branch's pin 6c7f996a4fe stands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
Japabu added a commit that referenced this pull request Oct 2, 2026
Cargo.lock alone conflicted: both sides kept, main's pcap-file and
byteorder_slice beside this branch's ureq, flate2 and tar trees, and cargo
left the result as it resolves the merged manifests. Against origin/main the
lockfile differs by what it did before the merge, +801 -6.

#659 moves the rust gitlink and the kernel, so the freestanding and sysroot
keys move with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
Japabu added a commit that referenced this pull request Oct 2, 2026
…among them) into wt/toyos-winitstall

Three content conflicts, each a deletion on main's side of a block this
branch had edited:

- kernel/src/inbox/mod.rs: main deleted `Staged`, `handler-post`'s ring,
  with the actuator (#660). This branch's hunks inside it — the `owed`
  field, `Poll::new`, the `WatchFlags` direction and "fires" for
  "completes" in its doc — adapted it to the ring's new fields and go with
  it. `Inbox::complete` keeps this branch's wording and loses main's
  `raise_if_staged` call.
- kernel/src/watch.rs: main deleted the `handler_post` module, `holding`,
  `note_post` and the `raise_if_staged` call in `IrqLock::with`. This
  branch's one hunk inside it was "fires" for "completes" in the module's
  doc, which goes with it.
- userland/fsd/src/main.rs: main deleted the four test actuators
  (`--end-on`, `--end-at-read`, `--end-at-mount`, `--let-go-at-read`) and
  kept the acceptor probe; this branch deleted the probe and kept the
  actuators. Both deletions stand: no `caps_len`, no `probe` field, no
  actuator field, and `accept` is this branch's.

Two resolutions no marker asked for:

- src/ci.rs: #668 made a control's verdicts `Fails(..)` values, so
  `post-is-an-answer`'s three verdict strings become three `Fails`.
- issues/build: main filed the C++ runtime's scratch removal as an issue
  of its own (`the-cxx-runtimes-scratch-removal-dies-on-a-finder-file.md`)
  beside the sweep's, which this branch had merged into one file. Main's
  two files stand and this branch's file goes.

The `rust` gitlink is main's, `95960d6c2`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
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