# Checklist: windows-ioring-sys
Design decisions are in [DESIGN-NOTES.md](DESIGN-NOTES.md); the session that produced them is
[DESIGN-SESSION-2026-08-22-ioring-architecture.md](design-sessions/DESIGN-SESSION-2026-08-22-ioring-architecture.md).
Everything through M18 is archived in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md): M1-M6
[here](COMPLETED-CHECKLIST.md#moved-2026-08-22----m1-through-m6-ring-lifecycle-through-consumer-documentation),
M7 [here](COMPLETED-CHECKLIST.md#moved-2026-08-23----m7-ring-copy-a-topology-aligned-sample), M11-M14 in their
own dated groups, M8-M10
[here](COMPLETED-CHECKLIST.md#moved-2026-08-30----m8-through-m10-handle-lifetime-cross-ring-identity-and-the-contract-audit),
and M15-M18
[here](COMPLETED-CHECKLIST.md#moved-2026-08-30----m15-through-m18-the-testing-strategy-response-to-eight-defects).
**Only `M6+` remains, and it is parked rather than pending** -- see the `M{n}+` convention: it is gated work
with no current obligation, not an unfinished milestone. `M19` below is complete and awaits archival with the
next group.
## M19 -- Close the `get()` borrow hole (was release-blocking for 0.2.0)
Found by the code review of the M15-M18 branch, and **measured**: safe code can hold the `&[u8]` that
`RegisteredBuffers::get` returns across a submit that makes the kernel write into that same buffer. A probe
observed the bytes change from `0x11` to `0xEE` through the live borrow while a *fresh* `get(0)` at that same
instant correctly refused with `WouldBlock`.
The [D-36](DESIGN-NOTES.md#d-36) fix checks `kernel_writes` at the instant of the call, but returns a slice
whose lifetime is tied to `&self` -- and `Batch::read_registered` takes the registration by **shared**
reference, so the borrow and the read coexist. `get_mut` is unaffected: `&mut self` conflicts with the shared
borrow, so the compiler already rejects the analogous sequence. Only `get` is exposed.
This matters more than its severity alone suggests: it is the same hazard class D-36 was filed to close, in
the API whose breaking change 0.2.0 is being cut for, and it is reachable with no `unsafe` anywhere.
- [x] **M19.1** -- Make the borrow's *existence* conflict with starting a read into that buffer, not merely
its creation. The options differ in what they cost, and the choice is the engineer's:
(a) `get(&mut self)`, which is one line and closes it completely, but forfeits the concession D-36
deliberately kept -- a caller could no longer read a buffer while its own *write* is in flight, which is
sound because a write means the kernel only reads;
(b) a `with_bytes(i, |bytes: &[u8]| ...)` callback, or a returned guard type holding a reader count that
`begin_use(KernelAccess::WritesBuffer)` then refuses against, which preserves (a)'s concession at the cost
of a wider API change.
**Chose (a), after the engineer's question collapsed the choice:** does a caller ever *need* access during
the hazard window? No -- while a read is in flight the bytes are indeterminate, partially written in
arbitrary order, and only become meaningful once the completion is observed; while a write is in flight the
caller wrote them and already knows. Earlier or later is always available, so the concession (b) preserves
has no legitimate use.
**The codebase agreed before the change was made:** all ~40 read sites across tests, examples and the
epoch-log sample already read at a quiescent point -- their own `expect` messages say "is quiet", "is quiet
again", "neighbour slot is quiet". Converting them needed nothing but `mut` on ten locals; not one held a
borrow across a submit.
The SAFETY comment now states an invariant the signature actually provides, and the rustdoc explains why
`&mut self` hands back a shared slice.
**The arena pattern is intact**, which was the thing worth checking: a `Token` holds a `RegisteredUse`, not
a borrow of the registration, so quiet neighbours stay readable while operations are outstanding.
- [x] **M19.2** -- Add the regression test the probe became: hold the borrow across a submit and assert the
bytes cannot change, or that the sequence no longer compiles. Verify it by sabotage like every other
instrument in M15-M18 -- reverting the fix must turn it red.
**Done as a `compile_fail` doctest on `get`**, since the hazard is now a type error rather than a runtime
one. **Sabotage-verified:** reverting the signature to `&self` makes it fail -- the sequence compiles again,
which is exactly the hole.
Paired with a `no_run` doctest asserting the *neighbour* case still compiles, because a `compile_fail`
passes on any error and a guard that over-constrained the arena would look identical to one that did not.
- [x] **M19.3** -- Sweep for the same shape elsewhere. The M18.1 audit asked what a returned value *permits*,
not how long its borrow *lasts*; those are different questions and only the first was asked. Re-check every
borrow-returning entry in [BORROW-SURFACE.txt](BORROW-SURFACE.txt) against the second, and record the
distinction in [DESIGN-INSTRUCTIONS.md](DESIGN-INSTRUCTIONS.md) so the recurring question covers both.
**Swept all seven entries; `get` was the only one.** `get_mut` never had it (`&mut self` already conflicts
with the shared borrow `read_registered` needs -- confirmed by compiling the analogous sequence and getting
`E0502`). `RingScope::batch` and `EventDelivery::scope` both borrow exclusively and confine what they hand
out. `RingContract::violations` holds no kernel resource and every `observe_*` takes `&mut self`.
`IoRingError::name` returns `&'static str` from a literal.
[DESIGN-INSTRUCTIONS.md](DESIGN-INSTRUCTIONS.md) now poses **both** questions, with a mechanical form for
the second -- take the borrow, then try to call everything that could start work against the same object --
and D-45 is added to its table of shipped defects of this shape.
**Swept the count restatements too:** that file said "three defects" in four places and is now four, which
is the restatement drift the repository's own conventions warn about.
## M6+ -- Model B: explicit-thread delivery and affinity
Parked, not pending. Deferred by the engineer's explicit direction during the 2026-08-22 design session,
with the plan scoped now so the shape is not lost. This is **not** a fallback for a missing capability
(D-3) -- it is the high-performance architecture, and M4's thread-pool path is the convenient one.
- [ ] **M6+.1** -- `DeliveryMode::{ThreadpoolWait, PinnedThread}` as an explicit consumer choice, never an
automatic degradation.
- [ ] **M6+.2** -- Resolve the contention between a thread parked in `SubmitIoRing(ring, n, INFINITE, ..)`
and callers wanting to build SQEs. This is the hard part and the reason this is its own milestone: it
directly contradicts M3.1's `&mut`-enforced serialization, and needs either a submit-ownership handoff
or an internal lock. Neither is obviously right.
- [ ] **M6+.3** -- Shutdown: waking a thread parked on `INFINITE`. `IORING_OP_NOP` is supported and is the
wake mechanism.
- [ ] **M6+.4** -- Affinity: binding a ring's thread with `SetThreadGroupAffinity`, and documenting the
execution-domain pattern (one pinned thread, its ring, its node-local registered pool, its shard).
- [ ] **M6+.5** -- A test seam forcing the pinned-thread path even where the completion event is available,
so it stays testable on every machine rather than only on hardware that lacks the feature.
- [ ] **M6+.6** -- Decide `IoBuf`: extract to a shared crate, re-export from
`windows-overlapped-io-sys`, or leave duplicated (D-1). The merge-or-delete decision that duplicate-then-decide
defers to the point where the new path is proven -- which is here, not earlier.