Skip to content

Document NUMA design session findings and implement waitable queues - #96

Closed
MikeGrier wants to merge 405 commits into
mainfrom
mikegrier/deferred-namespace-ops
Closed

MikeGrier wants to merge 405 commits into
mainfrom
mikegrier/deferred-namespace-ops

Conversation

@MikeGrier

Copy link
Copy Markdown
Owner

No description provided.

Mike Grier and others added 30 commits September 1, 2026 17:36
Part of a fresh cargo-mutants run on this new crate (default features: 88
caught, 61 missed, 0 timeout, 91 unviable). This commit closes the two files'
share -- 9 of the 61.

capture_set.rs: two genuinely equivalent mutants, documented rather than
chased. `IMPERSONATION`'s `1 << 0` swapped to `1 >> 0` produces the
identical value (a shift by zero is its own inverse); `DEFAULT`'s `|`
swapped to `^` cannot differ either, since the two bits it combines are
disjoint by construction. Both confirmed by sabotage: the mutated crate's
whole suite passes unchanged. The three real `Debug` gaps (`index >
0`'s comparison flipped three ways) survived because the existing test only
checked that both aspect names appeared, which a misplaced or doubled
separator does not disturb -- pinned the exact rendered string instead,
including the single-aspect case where `index` is only ever 0.

declared.rs: the same "every real host agrees with the wrong constant" shape
recurs on `is_empty` (asserted only with two of three fields set, which
cannot tell the correct `&&` from the one still-missing `||`) and on
`DeclaredGuard::release`/`restore`'s `ok == 0` (every existing path only
restores what installed cleanly). Redirection's revert is this crate's one
deterministic failure -- there is no redirector at all in a 64-bit process --
so the guard is built directly from its private fields to reach it without
going through `install` first. `Drop` for the guard and the private
`release_background` helper had no test forcing an unreleased guard out of
scope or calling the helper directly; both are exercised through memory
priority, which always installs and restores for real. `DeclaredError`
gained a `#[cfg(test)] pub(crate) synthetic` constructor (mirroring this
crate's own `without_os_error` and the workspace's established
`Completion::synthetic` precedent) so `raw_os_error`/`Display`/`source`
could be tested without provoking a real Win32 failure.

All nine sabotage-verified: each mutation re-injected on its own line, the new
test fails with it and passes without it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Continues the cargo-mutants sweep on this new crate; error_mode.rs's share of
the original 61 missed.

Documents \SUPPORTED\'s two \|\ vs \^\ mutants as equivalent, same reasoning
as capture_set.rs's: \SEM_FAILCRITICALERRORS\, \SEM_NOGPFAULTERRORBOX\ and
\SEM_NOOPENFILEERRORBOX\ are pairwise disjoint bits (0x0001, 0x0002, 0x8000),
so no combinator can distinguish \|\ from \^\. Confirmed by sabotage: the
mutated crate's whole error_mode suite passes unchanged either way.

\ApplyError\ and \RestoreError\ had no test at all for \
aw_os_error\,
\Display\, or \Error::source\ -- every existing test only reaches the happy
\�pply\/\
elease\ path, and this crate installs only bits Windows accepts, so
there is no reachable failure through the public API. Both gained a
\#[cfg(test)] pub(crate) synthetic\ constructor, matching declared.rs's
\DeclaredError::synthetic\ from the previous commit.

\ErrorModeGuard::release -> Ok(())\ survived for the same reason: every
existing release restores a mode Windows itself reported for this thread,
which always succeeds. \SetThreadErrorMode\'s one documented rejection --
\SEM_NOALIGNMENTFAULTEXCEPT\ -- is exactly the bit \ThreadErrorMode\ refuses
to represent, so the guard is built directly with the raw, unrepresentable
value rather than through \�pply\, and \
elease\ is asked to restore it.

All four real gaps sabotage-verified: each mutation re-injected on its own
line, the new test fails with it and passes without it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…one that stays

Continues the cargo-mutants sweep on this new crate.

TransactionError gained the same \#[cfg(test)] pub(crate) synthetic\
constructor as DeclaredError and error_mode's ApplyError/RestoreError, closing
its \
aw_os_error\/\Display\/\Error::source\ gaps the same way.

\TransactionGuard::release -> Ok(())\, its \Drop\ replaced with an empty
body, and the \!\ deleted from that same \Drop\ all survived for a subtler
reason than the others: an ordinary test thread already carries no
transaction, and \Captured::Absent\ installs "no transaction" too, so the
entry state and the installed state render identically through \live()\ --
a guard that restores nothing is indistinguishable from one that genuinely
does, regardless of what the test checks. A real transaction as the entry
state (via this file's own \Transaction::new\/\while_transacted\) is what
makes "restored" and "still cleared" two different, checkable things; the
first attempt at this test used the ambient no-transaction baseline and, as
predicted, did not catch any of the three on the first sabotage pass -- caught
by re-running the sabotage rather than trusting the first green result.

\is_supported -> true\ is recorded rather than chased. \
tdll.dll\'s
\RtlGetCurrentTransaction\/\RtlSetCurrentTransaction\ are undocumented but
long-stable exports present on every Windows version this crate has been
tested against, so the honest answer and the constant agree on every reachable
host, and \KTM\'s cache is a process-global \OnceLock\ with no seam to inject
a disagreement without a production-code change out of scope for a test pass.

All real gaps sabotage-verified: each mutation re-injected on its own line,
the new test fails with it and passes without it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…this sweep

Finishes the cargo-mutants sweep on this new crate: capture_set.rs,
declared.rs, error_mode.rs, and transaction.rs were closed in the four
preceding commits; this is state.rs's share.

\CaptureError::raw_os_error\'s four constant mutants needed two constructed
cases together: the \ErrorMode\ arm hardcodes \None\ (there is no OS code
for an unrepresentable bit), which alone rules out \Some(0)\/\Some(1)\/
\Some(-1)\ but agrees with an unconditional \-> None\; a \Transaction\ arm
built with \TransactionError::synthetic\ supplies the \Some(5)\ that rules
that one out too. \state::ApplyError\ needed only one constructed variant,
since every arm forwards unconditionally -- one non-trivial \Some\ rules out
all four constants and proves \Display\/\source\'s real bodies ran.

\RestoreReport::is_clean\'s two \&&\ and its three accessors are the same
"asserted with two of three fields set, or only through a real clean
\with_applied\" shape as \Declared::is_empty\'s gap: one field set at a time
is what a wrong operator or a wrong constant cannot survive. The three
accessors are covered by the same three constructions, using the
\RestoreError\/\DeclaredError\/\TransactionError\ synthetic constructors
added over the last three commits.

\
elease_error_mode -> ()\ is recorded as equivalent rather than closed: it
is documented in place, at the mutation site, because \ErrorModeGuard\'s own
\Drop\ restores identically to what \
elease\ does the instant an
unreleased guard is discarded, and this function discards \
elease\'s
Result either way -- so the mutant's behaviour and the real body's agree in
every case reachable through this function, confirmed by running the
existing behavioural test against it. The test is kept anyway, reframed as
pinning the function's intended behaviour rather than as a mutation kill it
cannot be.

All seven real gaps sabotage-verified: each mutation re-injected on its own
line, the new test fails with it and passes without it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Completing the M15.6 sweep left `missed` as the only column that indicates a
real gap, and adjudicating the residue found three.

`freed_resumers`' `!took_one ||` guard: `&&` binds tighter, so the mutant reads
`(!took_one && resumers.is_empty()) || room != 1`, which differs only when a
take that took nothing lands on the edge. `Receiver::try_recv` is the sole
caller that can report `took_one == false`, so the mutant lets an empty poll
prod a parked producer -- waking it with no room to use.

`Debug for Receiver`: both an empty body and an inverted `disconnected` flag
survived. This impl is the only outside view of the queue's occupancy and is
what someone diagnosing a stalled watcher reads; an inverted flag is worse than
no flag, because it misleads at the moment it is consulted. The new test asserts
the rendered string whole. `Debug for Sender`'s empty body folds into the
existing opaque-handle test, which already carried this rationale.

Each was verified by re-injecting the mutant and observing a red suite, not by
reasoning. The remaining 4 missed mutants are all in `StandingHold::drop`, whose
body is unreachable; that is M15.1, an engineer decision rather than a test gap.

Also spawns M15.7 for what the sweep measured but cannot fix with tests: 14 of
the run's 20 minutes are mutants scored `timeout` that were in fact already
detected, between 4 and 132 tests having failed before the kill. The lever is
the test-side wait budget, and that trade against flake-resistance is the
engineer's to make.

Completed item: M15.6: Finish converting `queue/tests.rs` to bounded waiting, so a broken wake fails instead of hanging.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ng claim

A fresh full-crate cargo-mutants verification run (post-sweep, via
tools\run-mutants.ps1) surfaced two things the four preceding commits missed
or got wrong.

\CaptureError::raw_os_error\'s prior fix only asserted the constant-ruling-out
value; \Display\ and \Error::source\ on the same type had no test at all,
and were genuinely missed in the original 61-item list -- an oversight in the
initial triage, not a new regression (both appear in the run that started this
sweep, at lines 164 and 175, simply not grouped with the other five types'
identical gaps at the time). One constructed \Transaction\ variant proves
both, the same as \state::ApplyError\'s equivalent fix.

The bigger correction is to \TransactionGuard::release\/\Drop\'s test, whose
prior commit message claimed the mutation's effect was "thread reuse in the
test harness" and fixed it by moving the check onto a dedicated thread. That
diagnosis was wrong, caught by re-verifying with a full sweep rather than
trusting the earlier sabotage runs: cargo-mutants' own invocation of the exact
same mutation still reports the mutant as missed, on the *dedicated-thread*
version, even though a direct \cargo test\ -- run 25 times clean and 10 times
against the mutation, every time -- shows it failing reliably every time,
taking three other, unrelated transaction tests down with it. The dedicated
thread does not stop that spread (nothing in a test can), it only stops this
test's own pass/fail from depending on another test's unrelated state. Since
this workspace's CI runs \cargo test\, not \cargo mutants\, and that is the
verification this repository's own instructions call authoritative, the test
stands on that basis; the comment now describes the measured process-wide
spread rather than the disproven thread-reuse theory, and the discrepancy
against cargo-mutants' own harness is left unexplained rather than papered
over with a wrong claim.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…h a tripwire

Mutation testing left four survivors in `StandingHold::drop`. The interesting
part was not the survivors but why they survived, and the answer changed what
the right fix was.

Reachability, from the source: every hold is built in one place and moved
straight into `state.queue`; the only site that removes an entry from that
queue is `take`, which settles the reservation inline with the pop and sets
`resolved`. The only other way a hold dies is `Shared` being torn down, where
the `Weak` fails to upgrade. Nothing reaches the body in between -- confirmed
by replacing it with `unreachable!()` and passing the full suite.

History, from git: `4198aa8` says this `Drop` restored `reserved` *on drain* --
it was the drain path. `07d4b75` found that popping exposed the queue slot
before the deferred `Drop` restored the reservation, moved the release into
`take`, and left `Drop` as "the fallback for every other discard." Live code
whose only caller moved out from under it.

The finding that decided the outcome: the body could not have run safely.
`take` takes `&mut State`, so its caller holds the `items` guard -- and any
other way to remove an entry needs that same guard, so a hold discarded on such
a path is dropped inside the lock, and the body's first act was a plain
non-reentrant `lock(&shared.items)`. Measured, identical forced unwind out of
`take`: body live hung past 90s; `Drop` short-circuited failed immediately. The
"fallback for every other discard" would have deadlocked in exactly the
situation it was written for.

Building the discard path was never an option either -- it would contradict
`dropping_a_standing_slot_while_its_message_is_still_queued_releases_capacity_once`,
which asserts a cancelled slot's queued question still arrives.

So the body is replaced by `debug_assert!(std::thread::panicking(), ...)`: the
true statement rather than a bare `false`, so the one way to arrive today lets
the original panic propagate instead of becoming an abort from a second one,
while any other arrival fires. It encodes the contract the deadlock taught --
a discard must release under the `items` lock it already holds, as `take` does.

All four survivors are gone, three by deletion and the whole-impl mutant by the
new tripwire test. Every branch the new `Drop` admits was injected and confirmed
caught. Swept the four other statements of the same fact -- `Entry`,
`StandingHold`, `StandingState` and `take` all described this `Drop` as the live
release mechanism, and none had moved when the fact did.

Completed item: M15.1: Decide whether `StandingHold::drop`'s release path is dead code, and act on the answer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Completed item: M1.1: Add direct unit coverage for the 14 surviving mutants in encoding defaults, capacity management, comparison and hashing forwarders, and borrowed OsString conversion; sabotage-verify the tests and rerun cargo mutants for wtf-string.

Mutation sweep: 132 tested, 97 caught, 35 unviable, 0 missed, 0 timed out.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… impossible

D-80 recorded that a handle from `OpenFileById`, once armed, "reliably fails to
resolve the fault it was reopening for", attributed it to "the OpenFileById
handle's interaction with IOCP association/arming specifically", and recorded
the cause as not understood. That attribution was wrong, and it pointed future
work at the wrong subsystem.

A standalone probe -- no thread pool, no IOCP, no crate code -- reproduces it
with `CreateFileW`, `OpenFileById` and a bare `ReadDirectoryChangesW`:

  CreateFileW, by path                             TRUE (pending)
  OpenFileById                                     FALSE, 87
  NtCreateFile by id, with FILE_DIRECTORY_FILE     FALSE, 87
  NtCreateFile by id, without it                   FALSE, 87
  control: NtCreateFile by NAME, same options      TRUE (pending)

All five handles are identical everywhere it is natural to look: every one is
asynchronous (FileModeInformation), every one granted 0x00100081
(FileAccessInformation), every one resolves the same path
(FileNameInformation). The only variable that changes the outcome is whether
the object was resolved by file ID or by name. That also disproves the two
other readings on the table -- it is not SYNCHRONIZE or volume-hint semantics,
and the by-name control through the identical NtCreateFile call rules out a
defect in the measurement.

Confirmed against the crate: re-enabling the fast path fails six tests, every
one a fault-resolution test, with "the fault never resolved after being
answered", and instrumenting the arm prints code 87. The read never completes
because it never starts.

So the path is removed rather than disabled. It could not have paid for itself
in any case: `reopen_via_existing_handle` returned its candidate only when the
reopened object's path already equalled the watcher's recorded canonical path,
so by construction it could only ever hand back a handle to the object at the
path the path-based fallback already opens. Gone with it:
`DirectoryHandle::reopen_by_id`, `DirectoryId::file_reference`, and the four
`reopen_by_id_*` identity tests that characterised the mechanism.

What replaces them is `tests/reopen_by_id_cannot_be_watched.rs`, which asserts
the OS limitation itself with the by-name control alongside, so this decision
rests on something that executes rather than on a paragraph. It also settles
the mutant that started this: `OpenFileById` on a directory succeeds with flags
= 0, and the flags' only effect is sync-vs-async -- observable exclusively
through an I/O this handle can never perform.

Swept every other statement of the fact: D-80's decision row and detail
section, the reopen paragraph above it, the docs on `directory_id`,
`canonical_path` (both), `retry_reestablish` and `on_path_based_reopen`, a
`monitor::tests` comment, and the M11.1/M11.2/M11.5 entries. `COMPLETED-PLANS.md`
is left as dated, append-only history.

Spawns M15.8: `WatcherInner::canonical_path` is now written and never read, and
no warning will surface it, because `lock(&self.canonical_path)` counts as a
read of the field.

Completed item: M15.2: Explain, then either fix or document, why a handle from `reopen_by_id` rejects the very read the watcher exists to issue.
Completed item: M-inf.4: Root-cause and, if fixed, re-enable M11.2's fast reopen path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…query

M15.8 was framed as remove-both or keep-both, because the stored field and the
`DirectoryHandle` query looked like one unit. They are not: the only reader
worth having reads the new handle, not stored state, so the two settle
differently.

The field goes. `WatcherInner::canonical_path` was written on every install and
read by nothing -- its one reader was the by-reference fast reopen D-80 removed
-- and no warning would ever have surfaced it, because
`lock(&self.canonical_path)` counts as a read of the field. That is the same
invisible-dead-code shape as M15.1's unreachable `Drop`.

The query stays, and gains a caller that uses its result. The "reopened on a
different volume than before" warning printed `self.path`, the client-supplied
string that `WatcherInner`'s own doc comment calls "possibly not even fully
resolved" -- the one moment that string is least worth printing, because
changed resolution is exactly what happened. It now names the path the handle
resolves to.

It also had no tests at all: the four `reopen_by_id_*` tests were its only
coverage and went with M15.2, so keeping it meant keeping an untested Win32
helper. Two added -- that it reports where the handle actually is (compared
against the OS's own answer, not the opening string), and that a fresh query
follows a rename, which a cached implementation would fail.

Verified by injection, which also confirms the boundary this item had to
establish: `buffer.truncate(written)` -> `truncate(written + 1)` is now caught,
while `written < buffer.len()` -> `<=` still survives. That second one is
M15.3's mutant exactly, needing a 512+ unit canonical path to reach, so M15.3 is
left standing and unanswered rather than dissolved.

The transferable rule, recorded in D-80: a diagnostic wants the live handle, not
a cached copy, so needing the value is not a reason to keep the field.

Completed item: M15.8: Decide whether `WatcherInner::canonical_path` and `DirectoryHandle::canonical_path` survive M15.2's removal.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Completed items: SH-7.1, SH-7.2, SH-7.3, SH-7.4, SH-7.5, SH-7.6, SH-7.7

Each finding was verified against the source before being accepted, and every
behavioural repair is sabotage-verified: reverting it turns the suite red.

Completed item: SH-7.1: reserving_mpsc reported Full from a claim word that was
never current. push and reserve load the claim relaxed, then test room by
computing position.wrapping_sub(head); once other producers claim and publish
past a stale position and the consumer drains them, head overtakes it, the
subtraction wraps, and an EMPTY queue reports Full (recording a refusal) while
reserve returns None. Both now re-read the claim and retry when it moved. A new
CLAIM race hook drives that window deterministically on one thread.

Completed item: SH-7.2: the NUMA cross-check compared a count against a highest
identifier. GetNumaHighestNodeNumber reports the highest node number and Windows
does not promise dense numbering, so nodes 0 and 2 were reported as a parsing
regression on correct hardware. Observation now carries highest_numa_node and
compares highest against highest.

Completed item: SH-7.3: outermost_partitioning_cache called a level a partition
without checking that it is one. Deduplicating by equal processor set is right
for the measured L1i/L1d case but leaves distinct-but-overlapping sets, which a
consumer double-counts. A level now qualifies only when its distinct sets are
pairwise disjoint. Full coverage is deliberately not required, and a test states
why.

Completed item: SH-7.4: windows-waitable-queues could not build documentation on
docs.rs -- it is Windows-only and imports std::os::windows unconditionally, but
omitted the target metadata every sibling published Windows-only crate carries.

Completed item: SH-7.5: inject-mutant.ps1 replaced every occurrence on the line
rather than the first. It passed 1 to the STATIC [regex]::Replace overload, whose
fourth parameter is RegexOptions, so the value meant IgnoreCase; no static
overload takes a count. Replacement is now done by offset, an ambiguous line is
refused unless -Column names the occurrence, the baseline is verified green
before any verdict is trusted, --all-features is the default, and the mutating
write moved inside the guarded region.

Completed item: SH-7.6: run-numa-spikes.ps1 checked the exit code of cargo build
but not of cargo run, then decided vacuity by searching output for VACUOUS. A
crashed spike printed no such line, so the summary announced "NOT vacuous -- this
runner has more than one NUMA node" on the strength of a stack trace and the
script still exited 0. A failed run is now an instrument failure.

Completed item: SH-7.7: check-publishable.ps1 and inject-mutant.ps1 now route
output through one sink; run-sabotage.ps1 performs its patching write inside the
try whose finally restores the file; run-mutants.ps1 stamps its output directory
per run, so a second sweep of the same scope no longer overwrites the analysis
its documentation promises to preserve; and the placement probe's scratch
directories carry the process id, so two concurrent test processes cannot delete
each other's fixtures.

One further finding was checked and does not hold: GetSystemDirectoryW returning
exactly the buffer length is unreachable, since success excludes the terminator
and failure includes it and so exceeds the buffer. The guard is widened to >=
anyway, with the boundary written down so the next reader need not redo it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
M15.3 asked whether this crate should open paths longer than MAX_PATH. It had
offered "prefix `\\?\` in `wide_path`" as one coherent outcome; measurement says
that outcome is wrong, and that the item's premise was measuring the test
harness rather than the crate.

`\\?\` is a path parsing mode, not a length switch. Adopting it on a caller's
behalf changes what their path means, on paths that have nothing to do with
length -- measured against a short directory that opens fine today:

  C:/Users/.../dir       opens verbatim, ERROR_FILE_NOT_FOUND prefixed
  C:\...\dir\.           opens verbatim, ERROR_INVALID_NAME   prefixed
  C:\...\dir\subdir\..   opens verbatim, ERROR_INVALID_NAME   prefixed

Relative paths would stop resolving entirely, which this crate supports on
purpose: `open_file_target` normalises a bare leaf's empty parent to `.`.

And "a directory deeper than MAX_PATH cannot be opened" was a property of the
harness. Long paths without the prefix need the machine's LongPathsEnabled
policy *and* the application's longPathAware manifest. On this machine the
policy was already set; the same probe source, same machine, differing only by
an embedded manifest, went from ERROR_PATH_NOT_FOUND to opening a 300-character
path. A library cannot set its consumer's manifest, and a Rust test binary has
none.

So the pass-through is now a stated decision rather than an unexamined default,
documented where a caller meets it: `wide_path`, `DirectoryHandle::open`, and
`Session::subscribe`.

The decision also records the traversal rule and states plainly that it
schedules no work: Win32 has no relative open, so traversal must build child
paths that can exceed MAX_PATH even when the caller's did not -- but this crate
never lengthens a path (recursion is the kernel's, names stay relative per D-8,
and the only structural operation is `open_file_target`'s `parent()`, which
shortens). If traversal is ever added, the base is `canonical_path`, not the
caller's string, because GetFinalPathNameByHandleW returns the `\\?\` form after
Win32 has applied the caller's parsing mode. Checked rather than assumed: no
code compares the two forms today.

Spawns M15.9 (guard tests pinning the pass-through, so a future "helpful" prefix
fails the suite) and M15.10 (the junction fixture for the 512-unit retry -- the
back door M15.3 called hypothetical works, no elevation, a 53-character junction
resolving to a 578-character target).

Completed item: M15.3: Decide whether this crate should open paths longer than `MAX_PATH`, and note the consequence for `canonical_path`'s retry either way.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Completed item: SH-7.8: reply to every thread and resolve the ones addressed.

Seven further findings, each verified against the source before being accepted.

The crate-level `cfg(windows)` gate. The crate documentation states that every
public item is gated and that non-Windows builds produce an empty shell, and
nothing in the crate implemented that -- so the stated platform contract was
simply untrue. Gated every module and re-export, matching the sibling
Windows-only crates.

The ioring drop test was not race-free, and its comment asserted otherwise.
`DROP_RUNS` was a process-wide static read as `after > before`, so another
test dropping any ring between the two loads satisfied the assertion on its
own -- masking precisely the mutant the test exists to catch, since cargo test
runs tests as threads in one process. The counter is now thread-local and the
count exact. Sabotage-verified: removing the Drop body still turns the suite
red.

Three corrupted SAFETY comments. `spsc`, `slotwise_mpsc` and `reserving_mpsc`
each carried a stray `//` and a literal TAB where "teardown" should read -- an
escape-hazard artifact that obscured the rationale for forcing Sync onto a
field holding a boxed FnMut.

The remaining two changed PowerShell tools now route output through one sink,
completing what the previous commit started, and M34.2's inventory records that
the scripts are done rather than deferred -- with the reason the deferral does
not apply to them, and what the Rust binaries still owe.

A stale manifest comment called crates.io publication "an open decision" when
PT-5.3 decided it (yes, but not until the download path is walked) and PT-5.6
tracks the blocked publication.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rustdoc does not document items produced by a macro invocation, so a doc comment on the thread_local! is an unused_doc_comments warning -- which CI escalates via -D warnings, failing both the clippy job and the no-default-features ioring job. Verified against all three CI clippy invocations with warnings as errors, and against the encoding check's glued-doc-comment guard.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…erator

While settling D-85 (windows-file-watcher passes a caller's path to Win32
verbatim), it became clear that windows-file-enumeration-sys had independently
taken the opposite stance: it resolves ordinary paths, caps them at MAX_PATH so
acceptance does not depend on the host's longPathAware manifest, and tells the
caller to supply a fully qualified \\?\ path for anything longer.

Both are right, and side by side they look like an inconsistency somebody should
tidy up -- which is the failure mode. They follow from one question, does this
layer ever build a path the caller did not give it, and the answers differ:

- A layer that never constructs a path passes the caller's through verbatim,
  preserving their parsing mode, and lets long-path behaviour follow the host
  application's manifest, because that is the application's call. The watcher
  opens one handle per watched directory and lets the kernel do subtree
  recursion, so it never joins anything.

- A layer that will construct paths demands a form it can build on, up front,
  because Win32 has no relative open and a built path can pass MAX_PATH even
  when the caller's did not. Discovering that mid-traversal is the worst
  possible moment, so the contract front-loads it.

Recorded once in the workspace design notes, with D-85 and the enumeration
crate's D-7 cross-referenced to it in both directions. It also records the
conversion rule both crates reached independently: move into \\?\ form from
output Win32 has already normalised under the caller's own parsing mode
(GetFullPathNameW, GetFinalPathNameByHandleW), never by prefixing the raw
string -- after normalisation preserves meaning, before silently reinterprets
it.

Adds crates/windows-file-enumeration-sys/CHECKLIST.md with REVIEW-1, registered
in that crate's PLANS.md. It is a review item and schedules no change: lib.rs
says recursive traversal belongs in a separate layer that does not exist yet, so
the request path contract has never been exercised by the consumer it was
designed for. The questions it asks are whether the deliberate MAX_PATH cap on
ordinary paths stays out of the way once descent is real, whether moving into
\\?\ form mid-descent is specified for every namespace the crate accepts (UNC
needs \\?\UNC\..., and \\.\ device forms take no prefix at all), and whether
EnumerationRequest::path()'s resolved form is stated as the intended base rather
than left to be inferred.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…prefix

D-85 says a caller's path reaches Win32 verbatim. Nothing enforced it, and the
change that would break it -- prepending `\\?\` so longer paths work -- is one a
future contributor could make in good faith, because it looks like a pure
improvement.

Five guards now sit in a labelled `directory::tests` section: forward slashes, a
`.` component, a `..` component, and a caller's own `\\?\` path, plus
`opens_the_current_directory_by_relative_path`, which already existed and turned
out to be load-bearing for the same reason. Its comment now says so, so it is
not simplified away by someone who does not know.

Each asserts the resolved *identity* rather than `is_ok()`. A path that opens
the wrong directory is the failure worth catching, and `..` is where that bites:
under an ordinary parse it must land on the parent, and a test that only checked
"something opened" would accept a handle on the child.

The measurement that justifies writing them at all: with a blanket prefix
injected into `wide_path`, exactly those five fail and the other 33 tests in the
module pass. A helpful prefix would otherwise land looking entirely green.

Two things that run settled rather than assumed. A trailing separator survives
the prefix, so it is not a distinguishing case and was left out. And the guards
are precise -- they fail on the prefix specifically, not on incidental path
handling.

Deliberately absent, and stated in the section header so the omission reads as a
decision: a long-path test. A Rust test binary has no longPathAware manifest, so
a MAX_PATH-exceeding open fails in-suite regardless of machine policy, and
asserting that would pin the harness rather than the crate. M15.10 covers the
part that can be tested.

Completed item: M15.9: Guard D-85's pass-through with tests, so a future "helpful" `\\?\` prefix fails the suite instead of silently changing what callers' paths mean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… mutants

The item's premise was wrong, and checking it first saved the whole fixture.
M15.10 -- and M15.3 before it -- held that the 512-unit retry was unreachable
through this crate's API, so a junction pointing at a deep target was the only
way in. But D-85's pass-through means a caller's own \\?\ path opens past
MAX_PATH without the host carrying a longPathAware manifest, which M15.3's own
probe had already shown. So `open` on a long \\?\ path reaches the retry
directly: no junction, no reparse-point plumbing, no spawned mklink, no question
about elevation.

Two tests. One drives a ~560-unit path through `open` and asserts the grown
buffer carries the whole path rather than a truncated one. The second walks
every length from 508 to 516 units, sizing each fixture exactly by padding its
final component, so the boundary itself is pinned: 511 units is the last that
fits one call and 512 the first that needs the regrow.

The branch really was uncovered: making the regrow return an error fails exactly
one test and passes 38.

Two of M15.3's claims are corrected by measurement. Its `<` -> `>` mutant does
not loop forever; it is caught and fails fast. And `<` -> `<=` is not a gap but
an equivalent mutant -- Win32's two-call convention makes `written ==
buffer.len()` unreachable, since success returns the length excluding the NUL
and needs room for it, while a too-small buffer returns the length including it.
Confirmed rather than argued: an assert_ne! probe never fired across the whole
suite, including the 508-516 walk that straddles the buffer size exactly. That
reasoning is now a comment at the comparison so the next sweep does not
re-litigate it.

Completed item: M15.10: Test `canonical_path`'s 512-unit retry through the junction back door.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Completed items: SH-8.1, SH-8.2, SH-8.3

These three arrived as *suppressed* comments in two review bodies rather than as
inline threads, so none of them was resolvable and none would have shown up in a
"are all comments resolved?" check. Each was verified against the source, and
each repair is sabotage-verified: reverting it turns the suite red.

Completed item: SH-8.1: all five timed runs in the contention probe started the
clock before thread::scope spawned anything, and every worker began pushing the
moment it was created. At 50,000 pushes each, an early producer could finish a
long uncontended prefix -- or finish outright -- while the last threads were
still being spawned, so a row labelled 16 or 32 producers need never have had
that many contenders, and the interval included thread-creation cost. Every
participant now waits on a start barrier, the consumer included in the drained
runs, and the clock starts as the barrier releases. This matters because the
module's own header says these numbers decide whether two speculative queue
shapes get written and whether the two shipped shapes merge.

Completed item: SH-8.2: write_backup_to_new_file reserved the name with
create_new and then wrote into it, so a write failing part-way left a truncated
.json under the name a complete record would have had -- indistinguishable to a
collector, and stepped around by the next run's collision suffix. The bytes are
now published by rename: written to an exclusively-created temporary beside the
reservation, flushed, and moved onto it only once the write succeeded. A
reader sees the final name either absent or complete. A test seam makes the
post-reservation write failure reachable, since no test can fill a disk.

Completed item: SH-8.3: places_from_topology iterated class_of, which only core
domains populate, so an online processor with no core domain was silently absent
from the result -- and the documented core-id fallback beneath it, written to
keep group 1's cpu5 distinct from group 0's, was unreachable as a direct
consequence. It then defaulted absent NUMA membership to 0, which is right only
when the topology names no memory domain at all; where the real nodes are 1 and
2 it fabricated node 0, the precise failure this crate's own seam rule forbids.
It now iterates the online processors and refuses a topology that names memory
domains but not this processor's, so the conversion is fallible rather than
inventive.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
M15.4 asked to isolate LAST_WRITE and SECURITY or record that they cannot be.
They can be, and the item's reasons for thinking otherwise were both wrong. It
named the right measurement -- arm a watch with a single filter bit at a time
and see which fires -- and running it overturned its own premises:

  SetFileTime last-write only   -> LAST_WRITE alone
  SetFileTime creation only     -> CREATION alone
  SetFileTime last-access only  -> LAST_ACCESS alone
  DACL edit (icacls)            -> SECURITY alone
  same-length rewrite           -> SIZE and LAST_WRITE, not ATTRIBUTES

The archive bit was a red herring. ATTRIBUTES does not fire for a same-length
rewrite at all; SIZE reports it, because std::fs::write truncates before
writing, so the length does change on the way through even though the net
length does not.

And the DACL test already worked. A DACL edit fires SECURITY and nothing else,
and injecting the SECURITY-dropping mutant turns that test red. It was recorded
as not closing that mutant because the earlier check was not measuring what it
claimed.

Adds two SetFileTime tests, one touching only the last-write stamp and one only
the creation stamp. The handle is opened for FILE_WRITE_ATTRIBUTES alone rather
than for writing, because a write-access handle can set the archive bit on close
and would quietly undo the isolation. The creation-time test is the interesting
one: the mutants drop adjacent pairs -- LAST_WRITE+CREATION and
CREATION+SECURITY -- so a creation-only change is the single operation that
fails under both.

All six flag-pair mutants in ALL_NOTIFY_FILTERS are now caught, each confirmed
by injection, including the four previously assumed covered but never verified
individually. The section comment and both older tests' comments are corrected
where they stated the disproved claims.

A harness lesson, since the first probe produced two confidently wrong tables:
reusing one directory handle across probes makes every read complete instantly
against records buffered from earlier operations, so the first run showed
nothing firing and the second showed everything firing. A fresh handle per
probe, decoding records and matching by file name, plus a built-in sanity check
so a broken harness announces itself rather than producing a plausible table.

Completed item: M15.4: Isolate the last two notification-filter categories, or record that they cannot be isolated from outside.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…to it

The submission closure classified ReadDirectoryChangesW's result inline, so
nothing could observe that classification except by running a real watcher and
seeing what the pool did. Getting it wrong tells the pool to reclaim an I/O the
kernel will still complete, and the completion lands in a freed buffer -- so
detection depended on allocator behaviour rather than on an assertion. The same
mutation was recorded MISSED in one sweep and a crash in another, and two
crashes were scored as caught purely because the process exited non-zero.

`classify_submission` makes that classification a value: it takes the raw result
and a lazily-taken GetLastError and returns Result<Issued, io::Error>. Four
tests assert the whole contract -- a non-zero return is still pending (a packet
is coming even though the I/O finished), ERROR_IO_PENDING is pending, a genuine
failure carries the OS code through, and an error with no OS code falls to the
failure arm rather than matching the pending comparison. The success test passes
a last_error closure that panics, pinning the rule that GetLastError must not be
read after a success, where it holds a stale value from an unrelated call.

One detail worth recording, because verifying the fix is what found it. The
extraction first took a `bool`, leaving `ok != 0` at the call site -- and
injecting *that* comparison was still "caught" only by the process dying with
STATUS_HEAP_CORRUPTION, which is the exact failure mode this item exists to
remove, relocated by one operand. The function now takes the raw BOOL and does
the `!= 0` itself, so the convention sits inside the tested surface and the call
site has no comparison left to get wrong.

Every mutant in the classification -- `!= 0` to `== 0`, to true, to false; the
pending equality to `!=`; the failure arm to Ok(Pending) -- now fails with exit
101 in about a second each. None crashes, so this file's mutation score stops
depending on heap layout.

Completed item: M15.5: Assert the arming contract in `arm_detailed_read`, so a broken one fails a test instead of sometimes corrupting the heap.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s in remaining

Completed items: SH-9.1, SH-9.2, SH-9.3

Completed item: SH-9.1: both bounded shapes could report a length larger than
their own capacity. `len` reads the producer-side position and then `head`,
which are two instants rather than one, so a consumer draining past the sampled
position makes the wrapping subtraction yield a number near the integer maximum
-- a four-slot queue reporting four billion items through a public metric. The
comment beside it defended the overestimate as "safe in the direction that
matters for a backpressure gauge", which is true of a bounded overestimate and
not of usize::MAX. Both are clamped to the capacity now, so the skew still
resolves towards full while the impossible value is gone.

Completed item: SH-9.2: reserving_mpsc inherited Bounded::remaining as
`capacity - len`, and its `len` excludes reservations by design -- so an empty
queue of four holding one reservation answered four while only three items fit,
promising room for a push that is guaranteed to be refused. Overridden on both
handles and both trait impls, reading the packed claim word ONCE so the position
and the reservation count cannot be sampled at different instants (which is why
they are packed together in the first place). `is_full` is now defined in terms
of `remaining` rather than restating the rule beside it.

Completed item: SH-9.3: the pull request description framed the change as CI and
provenance work and mentioned windows-waitable-queues only under release
tracking, while the majority of the diff is that crate's public API and its
three lock-free queue implementations. Rewritten to lead with the shipped
surface, its scope, and its compatibility position.

The skewed load pair is written directly in the tests rather than raced for. An
earlier version of these tests drove `push` through the CLAIM race hook and then
called `len()` afterwards -- by which point the two values agree again, so it
asserted nothing. The sabotage sweep caught that, which is the whole reason for
running one; both tests now restore consistent state before the handles drop,
because teardown walks head..tail and an inverted pair sets it a usize::MAX-long
loop that hangs rather than fails.

Sabotage-verified: 4 of 4 caught.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… 32-bit

Completed items: SH-10.1, SH-10.2, SH-10.3

Completed item: SH-10.1: reserving_mpsc's BOUNDS_MAX was a flat `1 << 31`,
derived from the packed position's width alone. On a 32-bit target the
crate-wide WRAPPING_MAX_CAPACITY is `usize::MAX / 2` -- that is `2^31 - 1`, and
therefore NARROWER than the packing -- so the const assertion that no shape may
exceed it fails the build outright, for every capacity including small valid
ones. It is now the narrower of the two limits, kept a power of two so the value
stays one a caller could actually pass, with a new assertion pinning that.
Verified both ways against a real i686-pc-windows-msvc check: the old constant
fails with E0080, the new one compiles.

Completed item: SH-10.2: the previous round's fix reserved the destination with
create_new and renamed onto it. That closed the truncated-file case and opened a
worse one -- an empty file under the record's own name for the whole duration of
the write, and left there permanently if the process was killed in that window,
which contradicts the absent-or-complete guarantee its own doc comment claimed.
The content is now written to a temporary in full and published with a single
atomic no-replace MoveFileExW. std::fs::rename cannot express this: on Windows it
always passes MOVEFILE_REPLACE_EXISTING, so it would silently clobber a record a
concurrent run had already placed, destroying what the collision suffix exists to
protect.

Completed item: SH-10.3: the tool discovered the topology three times -- the plan
from one reading, the fingerprint from another, and core_affinity::measure from a
third -- so a processor going offline mid-run could leave the announced plan, the
recorded host, and the measured rows describing different machines with nothing
saying which. The plan and the fingerprint now derive from one discovery.
`measure` still discovers its own, deliberately: its documentation refuses a
measure_with(places) seam because a supplied list's processor numbers stay valid
on the real host while its node labels need not, so every pin would succeed and
real timings would be filed under fabricated labels. Its rows carry their own
places, so each row states what it measured.

Sabotage-verified: 2 of 2 caught on the publication guarantees (replacing an
existing record, and creating the destination before the content exists).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…missed

Completed item: SH-10.4: spsc implements Reserving as well, and the previous
round corrected only reserving_mpsc -- so reserving every slot left this shape
reporting the full capacity as available while both push and reserve refused.
The finding is the same one, on the shape that was not looked at.

Its Bounded impls now override `remaining` on the producer and the consumer
alike, `len` is clamped to the capacity as the other two shapes' gauges are (the
two position loads are not one instant, so a consumer draining past the sampled
tail made the wrapping subtraction produce a number near usize::MAX), and
`is_full` is defined in terms of `remaining` rather than restating the rule
beside it.

The trait's default now says outright that a Reserving shape must override it,
and why: a reservation withdraws capacity without becoming an item, so it never
appears in `len` and `capacity - len` therefore promises room that `push` is
guaranteed to refuse. That is the part that stops the next shape which reserves
from inheriting the same wrong answer silently, which is exactly how this one
survived the previous round.

Sabotage-verified: 3 of 3 caught -- dropping the reservation term, removing the
trait override so the default is inherited again, and un-clamping `len`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…guessed

M15.7 framed the test-side wait budget as a trade against flake resistance that
only the engineer could make. It is -- but it was being made with no measurement
of what the budget absorbed. Instrumenting every wait in the suite:

  idle                        median 0ms   p95 2.5ms   max 506ms
  + a full release build      median 25ms  p95  94ms   max 509ms
  3x concurrent + a build     median 0ms   p95 2.5ms   max 508ms

45 of 46 waits finish in 2.5ms or less, and the whole tail is one test gated on
the retry backoff timer at ~515ms -- structural, not notification latency. None
of it moves under 4x oversubscription, so the 30s budget was absorbing no
contention it needed to.

What it cost: when a mutation breaks delivery, dozens of these waits each burn
the full budget on the way to failing, and the suite overruns cargo-mutants'
kill deadline, so a detected mutant is filed `timeout` rather than `caught`.
Measured on one of them, changing only NOTIFY_TIMEOUT: 93.6s at 30s (killed),
42.1s at 10s, 31.8s at 5s.

Confirmed by a real queue.rs sweep, against the run that motivated the item:
caught 78 -> 89, timeout 14 -> 8, wall clock 20min -> 14min. (missed 8 -> 0 is
M15.1 and M15.8's doing, not this.)

Six consecutive full-suite runs under sustained concurrent release builds were
green. The residual risk is stated on the constant: this was one 12-core
machine, 5s is ~10x the structural outlier, and a slower runner is unmeasured --
if it flakes, raise the number rather than doubting the tests.

Also refreshes tools/run-mutants.ps1, whose parameter docs still said timeouts
dominate the wall clock and pointed at the multiplier. They no longer do, and
the general lesson replaces it: when mutants pile up in the timeout column,
suspect the suite's own wait budget first -- a timeout is usually a detection
that was not allowed to finish.

Spawns M15.11. All 8 remaining timeouts are pinned by one test,
no_wakeup_is_lost_under_a_concurrent_burst, whose wait is bounded while the loop
around it is not: each mutant makes its exit condition permanently false while
the doorbell stays signalled, so it spins. That is M15.6's defect one level up,
and no further budget reduction can reach it.

Completed item: M15.7: Decide the test-side wait budget, so a mutation sweep is not dominated by tests that correctly fail slowly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ater means

Completed item: SH-10.5: reserving_mpsc's publish sampled the queue depth from
its own position and a relaxed load of head -- ungated and unclamped -- so the
recorded peak could exceed the queue's own capacity.

slotwise_mpsc's twin is bounded by construction, and the difference is worth
naming: there the producer's acquire load of the slot's sequence
synchronizes-with the consumer freeing that slot, so the head read afterwards
cannot be older than `position - capacity + 1`. This shape has a second entry
point with no such edge. `Reservation::send` redeems without a room check, so
the only head its thread is ordered against is the one `reserve` read, which may
be arbitrarily old by the time the reservation is redeemed -- and a stale read
over-reports by however much was drained in between.

The sample is now gated on tracking (parity with the twin, and it keeps the
shared head line untouched when nobody asked for the metric), taken before
publication, and clamped to the capacity.

Observable::high_water's contract is corrected to say what all three shapes
actually deliver: an UPPER BOUND on the true peak, never below it and never above
the capacity. The error is one-directional because a stale read can only be
older, and that is the useful direction for the question the metric answers --
whether a capacity was ever close to exhausted. Counting exactly, as an explicit
current-depth counter would, puts a read-modify-write on a line shared by every
producer and the consumer into every push and every pop: the line this crate pads
its positions apart to keep out of the hot path.

The test writes the stale-head state directly rather than racing for it. A first
attempt looped the reservation path sixty-four times and asserted the bound,
which passes with or without the clamp on a coherent machine -- the sabotage
sweep reported both mutants as SURVIVED, which is what a sweep is for.

Sabotage-verified: 3 of 3 caught -- removing the clamp, moving the sample after
publication, and flattening the peak so the clamp could not be bought cheaply.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
GitHub reported the pull request as CONFLICTING. main had four commits this
branch lacked, and the conflicts were all in windows-thread-ambient-sys --
because main's M23.6 did the same mutation-gap work this branch had done
independently, and did it better.

main's version is the one kept wherever the two disagreed, and the reason is
worth recording rather than being inferred from the diff: this branch
DOCUMENTED its equivalent mutants, while main ELIMINATED them.

  - `IMPERSONATION: u8 = 1 << 0` became `= 1`, so there is no shift whose
    direction a mutation can flip.
  - `bit::IMPERSONATION | bit::ERROR_MODE` became `IMPERSONATION.union(...)`,
    and the error-mode SUPPORTED constant likewise, so there is no bare `|` for
    a mutation to swap for `^` over disjoint bits.
  - `release_error_mode(guard)` became a bare `drop(guard)`, which is precisely
    the equivalence this branch's comment on that function had recorded.

That is why main reached zero missed mutants where this branch had seven
documented survivors: the survivors were real, and the better answer was to
remove the ambiguity rather than to explain it.

Resolved hunk by hunk rather than by taking whole files. `git checkout
--ours/--theirs` replaces the entire file and would have discarded everything
git had already auto-merged -- in these test files that is both sides' tests,
which is exactly what had to be kept.

  - COMPLETED-CHECKLIST.md: both sides appended a section at the same place. It
    is an append-only archive, so both are kept, in date order.
  - transaction/tests.rs: the import lists conflicted while the test bodies
    auto-merged, so the merged file needs the UNION -- `install` is used ten
    times below the block and `TransactionGuard` five, and either side alone
    would not compile.
  - state/tests.rs: the test of `release_error_mode` went with the function.
    Not rewritten against `drop`, because error_mode's own suite already covers
    both routes and more thoroughly -- including an injected restore failure
    this branch's version had no way to reach.

Verified after resolution: the workspace compiles, clippy is clean with
`-D warnings` under both default and all features, the encoding check passes,
and the affected suites pass -- thread-ambient 151 unit + 8 + 13 + 3, queues 281
+ 8 + 1, impersonation-token 13 + 14 + 1, topology 111. main's fault-injection
module and its distinctive tests are present in the merged tree, checked by name
rather than assumed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
M15.7's confirming sweep left 8 timeouts, and all 8 were one test:
no_wakeup_is_lost_under_a_concurrent_burst. Its wait was bounded, but the loop
around it was not -- and every one of its exit conditions is an answer a defect
can pin, which leaves the doorbell signalled, so the bounded wait returns
immediately and the loop spins at full speed. A bounded wait inside an unbounded
loop is still an unbounded loop, which is exactly why M15.6 missed it: that
sweep looked for unbounded recv(), and here the recv was already bounded.

The loop now owns a deadline whose message names `seen`, TOTAL and all three
predicate values, so a future instance says which answer got pinned rather than
just that time ran out. `await_signal` also still waited 30s, the budget M15.7
had lowered everywhere else; it is now 5s.

The suite-wide sweep the item asked for found no second loop of that shape. Ten
candidates were flagged mechanically and all but this one are sound -- three
bound themselves with a `!remaining.is_zero()` assertion, one terminates because
the helper it calls panics, two are path-construction loops, and the rest are
drain loops or stop-flag threads. Worth noting that the first heuristic returned
a false negative on this very test, because `await_signal` in the body looked
like a bound: the check that works asks whether the loop asserts against a
deadline *it owns*, not whether it calls something bounded.

A near-miss that only injection caught. The first fix for the remaining
Receiver::recv timeout replaced three bare `assert!(receiver.recv().is_none())`
sites with recv_timeout. The suite stayed green, the hang went away -- and the
mutant went from timeout to SURVIVED, because those assertions are the only
thing exercising recv's own disconnect check. A detection had been traded for a
gap while every surface signal said otherwise. The correct bound keeps the
blocking recv and puts the deadline outside it, on a thread with an mpsc
handshake -- the idiom a_blocked_receiver_is_woken_when_the_last_sender_drops
already used. The mutant is now caught in ~11s.

The rule: when a test asserts that a blocking call returns, bounding it by
making the call non-blocking tests something weaker. Bound the wait around it.

queue.rs across the three items: caught 78 -> 89 -> 95, missed 8 -> 0 -> 0,
timeout 14 -> 8 -> 2, wall clock 20 -> 14 -> 11 minutes. The last two had 44 and
70 tests already failed before the kill and are the inherent floor: a mutant
that breaks the core makes most of the suite fail, and a suite of failures takes
longer than the passing baseline the deadline is derived from.

Completed item: M15.11: Bound the loop in `no_wakeup_is_lost_under_a_concurrent_burst`, not just the wait inside it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both were red on the merge commit; one is mine and one was already failing
before it, and the difference is worth stating plainly.

**rustdoc, mine.** The 32-bit `BOUNDS_MAX` fix documented its reasoning with an
intra-doc link to `WRAPPING_MAX_CAPACITY`, which is `pub(crate)` -- so a public
item linked to a private one and the `-D warnings` doc job refused it. The
reasoning is kept and the link is not: the ceiling is named in prose with its
value, which is what a reader of the public constant actually needs.

**windows-file-watcher, pre-existing.** `canonical_path_is_exact_on_both_sides_of_its_first_buffer`
has failed on every CI run since e7b3c4f introduced it, and passes on a
developer machine -- so the merge did not cause it and no local run would have
caught it.

The fixture built a directory whose literal `\\?\` spelling was exactly N units
and then asserted the reported canonical path was N units. Those are only the
same string when the base path is already canonical. A GitHub-hosted runner's
temp directory is an 8.3 short name, `C:\Users\RUNNER~1\...`, and canonicalizing
expands it to `runneradmin` -- three units wider, which is exactly the reported
`left: 511, right: 508`. The fixture now measures from the base's canonical
spelling, so the expansion is a no-op.

Verified rather than argued, because this host generates no 8.3 names and so
cannot reproduce the runner directly. A junction reproduces the same shape --
a base whose spelling differs from its canonical form -- and pointing TEMP at
one gives `left: 538, right: 508` without the fix (delta 30, the junction's) and
a pass with it, mirroring CI's delta of 3.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sors

places_from_topology accepted a topology whose domains do not cover every
processor and filled each gap with a value indistinguishable from a measured
one. Three fallbacks, one cause: an absence was read as a value.

A processor absent from every core domain was given a synthetic core id
derived from its group and number, which can equal a real core domain's id --
classify then reports two processors as SMT siblings when one's core is merely
unknown. Its efficiency class became 0, which is also a genuine Windows class,
so within_class_pair reports a same-class pair against a real class-0 core.
Its cache domain became None, which the type already means "no cache level
partitions this machine" -- so two processors omitted from an incomplete
partition compare equal and serialize a confident same-cache measurement.

The rule now distinguishes uniform absence from a gap. A machine that reports
no core domains at all, or no partitioning cache level, has told us something
true about itself and still converts. A machine that places every other
processor but not this one has told us nothing about this one, and the
conversion refuses: places_from_topology returns Err(UnplacedProcessor) naming
the processor and, in a new MissingPlacement field, which of core / cache
domain / NUMA node was missing. MissingPlacement is non_exhaustive.

Core and efficiency class are two spellings of one rule -- Topology::cores()
filters to DomainKind::Core, so a processor's class is known exactly when its
core is -- and an EfficiencyClass variant written for the second was removed on
discovering it is unreachable. Sabotage confirms the pair behaves that way:
removing either refusal alone leaves the suite green, because the other still
fires; removing both fails two tests.

Also fixes two defects in the mutation wrapper. Its output directory stamp has
one-second resolution, so two runs launched in the same second -- a script
starting several scopes at once, which is exactly the case that wants separate
output -- selected the same directory and interleaved their results; a short
random suffix now follows the stamp. And its cleanup matched WerFault /
WerFaultSecure / vsjitdebugger by name across the whole session, killing a
crash report the user was reading or a debugger attached to something else
entirely; it now records the ones already running at startup and skips them.

The reported mutants.out path doubling does not hold, and a comment records the
evidence: cargo-mutants treats --output as the parent and creates mutants.out
inside it, verified on disk as <stamp>\mutants.out\caught.txt with 22 lines
against the 22 caught the wrapper reported.

Completed items: SH-11.1, SH-11.2, SH-11.3, SH-11.4

Completed item: SH-11.1: Three fallbacks each invented an answer that reads as a real one
Completed item: SH-11.2: The mutation wrapper's output directory could collide
Completed item: SH-11.3: The wrapper terminated fault handlers it did not start
Completed item: SH-11.4: The mutants.out nesting finding does not hold; documented in place

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mike Grier and others added 26 commits September 9, 2026 19:01
…nterrupted probe

Completed item: M1.3: Verify by interruption, not by reasoning.

Completes M1, which is archived to COMPLETED-CHECKLIST.md and its PLANS.md row
updated.

**The unit test this item specified would not have caught a regression.** It
asked for "a renderer that panics mid-report still has its finished lines in a
`Captured`", and that passes under a buffered report too -- emit the buffer
after catching the unwind and it holds, which is exactly what the pre-M1.2 code
did. What distinguishes streaming is not what a reader has at the end but WHEN
the sink receives it.

So `a_line_reaches_the_sink_before_the_renderer_returns` observes the sink from
inside the renderer, through an `Rc<RefCell<..>>` shared with a `Report` impl,
and asserts a completed line is already there while rendering continues.
Restoring the old buffered `emit_report_to` fails it with its own message.

**The interruption half is measured, with a control.**
`probe-queue-contention` takes about 65 seconds; started with stdout redirected
and killed at 8 seconds, the streaming build had 114 bytes on disk -- the host
banner and the heading -- and the pre-M1.2 build, built from `246687e` and run
through the identical sequence, had 0.

The control is what makes it evidence. Reading 114 bytes from the new build
shows only that something was written; reading zero from the old one shows the
change caused it.

`TerminateProcess` was used rather than Ctrl-C, deliberately and as the stronger
case: Ctrl-C runs the default console handler and lets the runtime unwind its
exit path, while `TerminateProcess` runs nothing at all, so anything still in a
userspace buffer is lost outright. A report that survives it survives a Ctrl-C.

Recorded in DESIGN-NOTES with the reason it works at all: Rust's `Stdout` wraps
a `LineWriter` and flushes at each newline even when redirected, so a line has
reached the OS before the next is composed. Had stdout been block-buffered this
milestone would have needed a per-line flush as well.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rendered report

Completed item: M2.1: Add a report oracle to this crate: one shared executable
definition of the correlations that must hold between the parts of a rendered
report.

Seeded with the three correlations that are known to be real because each was
violated, and modelled on `windows-file-watcher`'s `ContractChecker` including
its as-many-must-accept-as-must-reject discipline: an alarm beside a
non-agreeing verdict is legal, a caveated claim under doubt is legal, and a
prose-only report is silence rather than violation. Over-constraining is the
same defect as under-specifying and fails in the more expensive direction,
because noise trains a reader to ignore the instrument.

It reads the rendered artifact and never the state behind it, which is the whole
point: in the original finding the state was consistent and the two renderings
of it were not.

It relates two things already visible in the report and re-derives nothing, so
it has no opinion of its own to go stale. The gating rule is the interesting
boundary. `CrossCheck::parse_in_doubt` is `!disagreements.is_empty() ||
!parse_incomplete.is_empty()`, and the NDJSON publishes `parse_incomplete` as a
count rather than the predicate -- so the oracle reads that count and the
`disagree` verdict, which are the two visible shadows of that definition. The
coupling is deliberate and is what M2.2's sabotage check must confirm.

**An oracle whose labels do not match the renderer reads nothing, finds nothing,
and passes everything -- a vacuous pass that looks exactly like success.** Three
things close that: the prose labels were confirmed against a live
`probe-topology` run, a test corrupts each double-rendered value in turn and
requires a violation so a drifted label fails loudly, and the oracle was run
against a real rendered report with the historical defect injected. It reported
the contradiction twice, once for the prose verdict and once for the NDJSON, and
reported nothing on the same report unmodified.

The first attempt at that injection silently did nothing, and nearly produced
the opposite conclusion. Its anchor was `cross-check:`, which does not occur --
the real text is `cross-check against independently read Win32 counters:` -- so
the "defective" report was byte-identical to the clean one, the oracle correctly
found no violation, and the obvious reading was that the oracle was blind. A
sabotage that fails to apply is indistinguishable from an instrument that fails
to fire, unless the injection asserts that it changed something.

Nothing is bound to the oracle yet; that is M2.2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… site

Completed item: M2.2: Route every test that renders a report through the oracle,
so the roughly twenty-five existing `report()` call sites inherit the checks and
every future one does too.

Bound inside `topology_report::report` and `report_unmeasured` under
`cfg(test)`, so all 26 call sites inherit it without being touched. Asserting at
each site instead would check 26 cases and rely on the 27th author remembering;
asserting in the renderer checks every case anyone writes later, including cases
written to exercise something else. That is not incidental: the original defect
was found by a reviewer reading two paragraphs together, not by a test aimed at
it, so the cases most likely to catch the next one are the ones nobody pointed
at it.

**Both directions of the sabotage were measured**, because a binding that only
moves when its own test moves is cosmetic:

- the correspondence defect with the binding in place turns **13 tests red**,
  all of them in `tests` and none in `report_oracle::tests`;
- the same defect with the binding removed leaves **all 173 passing**.

The defect used was the NDJSON emitting the processor count where the core count
belongs -- both renderings individually well-formed, which is why no per-part
assertion can see it. One of the 13 is
`every_report_carries_the_banner_and_title`, which exists to check the banner.

The second measurement is the one that matters. It says the existing suite
cannot see this class of defect at all, so the detection is genuinely new rather
than a restatement of assertions already present, and the binding is what
delivers it. Had only the oracle's own tests gone red, the binding would have
been reaching nothing.

`cfg(test)` rather than always-on is deliberate: a real probe run must still
print a contradictory report, because a self-contradicting report is a finding
about this probe and a panic suppressing it would destroy the evidence. The
real-host path is M2.3.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…inst the oracle

Completed item: M2.3: Add the missing integration test: run `measure()` against
the real host, render the report, and apply the oracle.

Composes the report exactly as `probe-topology` does -- two fingerprint reads, a
real `measure()`, `attribution` -- and applies the oracle explicitly, because an
integration test links the library without `cfg(test)` and so does not inherit
M2.2's binding.

It had to exist because all 26 in-crate call sites build their `Observation` by
hand, and a hand-built observation can only contain a state its author already
imagined. The oracle bound to those sites was checking correspondences over
cases chosen by the same person who wrote the renderer, while the defect it
exists for was a state nobody had imagined. `measure()` reads the actual host,
and on CI that is the whole hosted-runner fleet.

It asserts nothing about this machine, deliberately. A test expecting a
processor count or a cache level would fail on the next runner shape rather than
on a defect, and would be loosened until it asserted nothing. "The report does
not contradict itself" holds for every host, including one whose topology cannot
be read at all.

**A second test exists because the first can pass having checked nothing.** If
the renderer's prose labels drift from the oracle's, every lookup returns
`None`, every comparison is skipped, and the real assertion goes green. So each
of the four double-rendered counts is corrupted in this host's own report and a
violation is required for each -- corrupting one would leave the other three
pairs unguarded.

Verified by widening one prose label by a single space: the guard failed naming
`"packages":` and pointing at label drift, **and the primary test passed in the
same run**. That pairing is the argument for the guard -- the assertion that
matters went green while silently checking one fact fewer than it thought.

The corruption asserts it changed something before concluding anything, which is
M2.1's lesson applied rather than restated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…at it found

Completed item: M2.4: Explore, with the sparse matrix as the instrument, whether
the same correspondence failures exist for `Coherence`, `BracketOutcome` and
`Verdict`, and in the sibling probes' renderers.

The prediction that the matrix would be mostly empty held on the axis the item
named, and failed on one it did not.

Empty, and recorded as empty so nobody re-explores it: `Coherence` is never
rendered at all -- it feeds the verdict and appears in no prose line and no
NDJSON field -- and `BracketOutcome` is rendered once, in the banner. Neither
can contradict itself. `Verdict` was already covered. `reason`, `arch` and
`cores with SMT` have one rendering each.

The productive axis was facts rather than state enums. Six more were rendered
twice with nothing comparing them, and all six are promoted: NUMA domains and
those without processors, cache domains per level, the outermost partitioning
level, domains per policy, and the two independently-read Win32 counters against
the enumeration.

The counters are a different rule shape and the closest to what this probe is
for. They are read independently precisely so a mismatch is a finding, so a
counter contradicting the enumeration while the verdict reads `agree` is the
original defect in its purest form -- the report printing its own contradicting
evidence directly above a verdict denying it.

`GetNumaHighestNodeNumber` was examined and deliberately NOT correlated with
`numa_domains`: it is the largest node number, which the report itself says is
not a count, so comparing them would manufacture a disagreement on any machine
with sparse node numbering. A must-accept test pins the exclusion so it is not
"fixed" later. Over-constraining is the same defect as under-specifying.

Reading `caches` and `policies` forced the field reader to balance brackets
rather than stop at the first closer. `caches` is an array OF objects, so the
naive read returned only its first entry and would have skipped every later
cache level in silence; a test corrupts the last entry, which only a balanced
read can reach.

All eight promoted cells are proved live against this host's real report by the
M2.3 guard, which now corrupts each in turn and requires a violation.

Two of the existing tests failed on the way, and usefully: extending the fixture
moved the text their `.replace` anchors were written against, so the injections
silently became no-ops. They failed because they require a violation to appear
-- the same trap as M2.1's first injection, caught this time by construction
rather than by luck.

Queues M2.9 with the finding that goes beyond this milestone:
`probe-doorbell-cost` and `probe-request-cost` render every measured figure
twice, prose and NDJSON, with nothing comparing them. Extending the oracle past
one renderer is a design question about where it should live rather than a
mechanical follow-on, so it is raised rather than taken.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ne source

Completed item: M2.9: Decide how the oracle covers more than one probe, then
cover them.

**Decided: if two renderings must match, they come from a common source.** Not a
third oracle rule set. Both cost probes now walk the same
`Observation::timings` the prose table walks, and a `json_key` function decides
only what the machine-readable rendering calls each entry.

That is strictly stronger than extending the oracle, and cheaper. An oracle rule
finds a contradiction that already exists; deriving both renderings from one
value means there is none to find. It also deleted code rather than adding it --
ten hand-named NDJSON fields and a `get` closure are gone, because naming each
figure separately was exactly what made the two renderings independent
restatements of one measurement.

`json_key` panics on a label it does not know, which is the safety of the
scheme: a figure added to `measure` reaches both renderings or fails loudly, and
cannot reach one only. Verified by adding an unnamed timing -- the probe printed
its prose row and then died naming the missing key. That the row appeared before
the panic is M1.2's streaming, so the two milestones compose.

What is left to test is narrow, which is the mark of the right fix. The
derivation is structural in the source, so the remaining question is only
whether it survives rendering, formatting and the process boundary. One
integration test per probe runs the real binary and compares each table row
against its NDJSON field, reusing the crate's own `json_key` rather than
restating the pairing -- a test carrying its own copy would be checking the
copy, which is the defect this milestone is about.

Its emptiness guard fired on the first run: `request_cost`'s table has ratio
columns after the figure, so a parser requiring exactly two tokens matched
nothing and the test would have passed having compared zero rows. That is the
third time in M1-M2 a check written against vacuous passes caught one
immediately.

The topology report is deliberately not converted. Its two sides are not one
list rendered twice but many individually-formatted claims, several with prose
that has no NDJSON counterpart and vice versa, so a common source there is a
much larger change. The M2.1 oracle covers it, and M2.4's eight promoted cells
are what make that coverage real; converting it is a decision available later
rather than a gap left by accident.

Answers the M2.4 open question: it did generalise, and the answer was not to
move the oracle somewhere shared. What generalises is the principle -- the root
DESIGN-NOTES' preference for a derived fact over a restated one -- which needed
no shared mechanism to apply.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A probe run makes three independent discoveries of the machine, and the
banner naming the host was built from an endpoint. `attribution` compared
only the two endpoints, so equal endpoints printed an unqualified banner
with nothing establishing that the middle read agreed -- the line naming
the machine could describe a different topology from the body under it.

Construction rather than a third comparison, as M2.9 chose: `measure_observed`
is a sibling of `measure` returning the observation and the fingerprint of
the very topology it parsed, so `measure`'s six callers are untouched and
the banner cannot disagree with the body. The endpoint reads keep their
job: they bracket a wider window than `measure`'s counter bracket.

The M2.1 oracle gains the invariant too, because construction closes only
the read gap. The banner and the body remain two derivations from one
topology -- `Fingerprint::from_topology` and `observe` -- which have already
disagreed once, printing `0p` for a machine measured on four processors.
`check_banner_against_body` closes that on every rendered report.

Measured, not read: sabotaging the banner back to the endpoint turns
exactly one test red, and sabotaging the body count on a real report
raises `BannerDisagreesWithBody`, so the rule is bound to the renderer
and not just the fixture. The first attempt at the fix reintroduced the
defect by formatting `host:  {fingerprint}` inline, a second copy of a
line whose owner documents that exactly one place must produce it.

Completed item: M2.5: Make the banner describe the read the body describes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… it stays

The module doc claimed the call is lexical and then, in the next sentence,
described it resolving against the process current directory. Those
disagree: a lexical canonicalizer is a pure function of its input, and this
reads mutable process state -- the current directory, and for a
drive-relative path the per-drive current directory in the hidden `=C:`
environment variables. "Touches no filesystem" is the claim that holds.

Swept `lexical` across the workspace rather than fixing the reported site:
6 statements in this crate, of which the probes checklist item named one.
The identical sentence was also in path.rs, with four more restatements in
doc examples, tests, an acceptance comment and DESIGN-NOTES. One further
site in windows-platform-probes is left to its own commit, since this crate
is release-managed and that one is not this crate's to change.

D-18 records the decision half. A genuinely lexical canonicalizer exists --
PathCchCanonicalizeEx, or PathAllocCanonicalize -- and is cheaper, but it is
the wrong call: resolving against the current directory AT SUBMISSION is the
property this crate buys, and a lexical call would leave a relative path
relative for a worker to resolve later against a directory any thread may
have changed. It also records that whether the call enters the kernel is
NOT established -- the PEB and environment block are process memory, which
is a statement about the data sources, not a measurement.

Completed item: M2.6 (part 1 of 2): Say precisely what `GetFullPathNameW`
does, in the crate that owns it, and decide whether it is still the call
`prepare` wants.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sion

The workspace sweep for `lexical` found one site in this crate that the
namespace-request correction could not touch: device_map's printed prose
called path preparation "lexical resolution" while explaining why it does
not close the drive-letter hazard. The sentence is right about the hazard
and wrong about the mechanism, in the same direction as the two corrections
before it.

request_cost's module doc already declined to name a mechanism, which was
honest but left the question open one layer down. It now links D-18 in the
owning crate, which closes it: what the call does, why it is kept over the
cheaper PathCchCanonicalizeEx, and that whether it enters the kernel is not
established.

Completed item: M2.6 (part 2 of 2): Say precisely what `GetFullPathNameW`
does, in the crate that owns it, and decide whether it is still the call
`prepare` wants.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ages

Each said what was being attempted and not why it failed, which is the whole
of what a CI log offers someone who cannot rerun under a debugger.

Both rules from the peel hold at all four sites. The failing call is the
statement immediately before each assertion and nothing but a null test runs
in between, so the code belongs to that call and no other; and every
condition is genuinely an OS failure -- null from CreateEventW and
CreateIoCompletionPort, null or INVALID_HANDLE_VALUE from CreateNamedPipeW.

Swept the class rather than the reported sites. Five further assertions in
this crate match the shape and are deliberately left bare: they test values
returned by helpers that make several calls, or pure predicates with no
Win32 in them at all, where a code would name whichever call ran last rather
than whichever failed.

Completed item: M2.8: Carry the OS error in the remaining Win32 assertion messages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merge 1abcaaf resolved a conflict in ci.yml by joining the doorbell-cost
step's `if:` and `run:` keys onto a single line:

    if: '!cancelled()'        run: cargo run -p ... probe-doorbell-cost ...

That is not valid YAML, so Actions would have refused the whole workflow.
origin/main is clean and its runs are green, so the damage is confined to
this branch and never reached CI -- only because the branch is unpushed.

Queued M34.5 for the reason this took three commits to notice.
tools/check-workflow-refs.ps1 passed the damaged file: re-run against it,
exit 0 and the same "62 reference(s) across 5 workflow file(s)". It checks
references by regex and never parses the document, so nothing in the
repository validates that a workflow is well-formed. Scanned the other four
workflows for the same weld shape; there are none.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All twelve probe steps now carry
`if: "!cancelled() && steps.build.outcome == 'success'"`, against three before.

The item queued this as an operational trade: guarding means diagnostics
survive a failed test, at the cost that a plain `!cancelled()` also runs the
step when the BUILD failed, where `cargo run` cannot compile and grey turns
red. Gating on the build takes both halves instead -- a failing test still
emits, a broken build still goes quiet -- so the trade is not needed.

Caught by verification, not by reading: `id: build` first went on the
workspace build step in job `build-test`, while every probe runs in
`platform-probes`. `steps.build` does not cross jobs, so the condition would
have been permanently false and SILENTLY SKIPPED ALL TWELVE PROBES -- a
guard that reads as careful while disabling what it guards. The probes job
had no build step to gate on, since `cargo test` builds implicitly and its
outcome cannot separate "did not compile" from "a test failed", so an
explicit one was added there.

Swept the comments that restate the guard's rationale, both of which quoted
the old one-part expression.

Completed item: M2.7: Decide whether the other nine probe steps in CI should
carry `if: '!cancelled()'`, and apply or record the decision.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
M2's nine items are all complete, so the group moves to
COMPLETED-CHECKLIST.md under a dated heading and CHECKLIST.md is left as
the crate's durable home for future work with no open milestones.

Both plan indexes carried stale rows: the crate's said "not started" for
work that included all of M1, and the root's described M2 as remaining.
Each moves to its COMPLETED-PLANS.md, which the root file's own preamble
says is where a finished checklist's record lives until new work is
planned against it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All 48 items across the eight milestones are complete, so the group moves
to COMPLETED-CHECKLIST.md under a dated heading. Verified by count rather
than by eye: 48 checked and 0 open in the moved block, 0 checked and 3 open
in what stays, and every M22-M29 item id still resolves in the archive.

The file is NOT deleted, which the feature-file convention would otherwise
call for. Its remaining `M26+` items are parked rather than pending -- each
is gated on the namespace-facility design branch reaching `main` -- so the
preamble now says where the completed milestones went and why the file
survives them. The PLANS row said "in progress" against a description of
work that is now finished; it names what is parked instead.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
M1, M1B, M1C, M2, M3, M4 and M36 are complete -- 30 items -- so they move
to COMPLETED-CHECKLIST.md. M36 is archived beside M1-M4 despite its later
number because it is likewise finished; the numbering records authoring
order, not execution order.

Verified by containment rather than by eye: both moved blocks appear
verbatim in the archive, and what stays holds 13 open items across M5, M5+,
M6 and M7 with the 5 checked items that belong to those still-open groups.

The PLANS row still gated the whole tool's description on work now done,
and buried that M5+ is withdrawn. It now names what is archived and what
actually remains.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
M30 (the queue crate's name, skeleton and SPSC shape) is complete, so its
five items move to COMPLETED-CHECKLIST.md; the moved block is verified
present verbatim. M31's seven finished items deliberately stay put: they
belong to a group that is still open, and converting them to stubs is
already queued as M34.3.

M35 in CHECKLIST.md held nothing but a one-line stub whose target was
archived on 2026-09-04. A stub carries no information the archive does not,
so the milestone goes with it rather than being migrated again. Its heading
also ran straight into M37's with no blank line between them.

The one surviving mention of M35 is left alone: it is M37's note that this
branch carries M34 and M35, which is a statement about numbering history
and still true.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…klists

A sweep of every CHECKLIST*.md in the repository found three more groups
with no open items, each archived in its own file's established style:

- windows-file-watcher M10, M11 and M15 (21 items). This file keeps a
  pointer heading per archived milestone rather than deleting it, so M10,
  M11 and M15 now read like M4-M9+ already did. Linked by explicit HTML
  anchor, not by the heading's generated slug: a first attempt built the
  slug by hand and got it wrong, which is what the anchor rule exists to
  prevent.
- windows-ioring-sys M19 (3 items). Its preamble already said M19 "is
  complete and awaits archival with the next group"; that sentence is now
  a link to the group.
- ship-topology M16 (13 items), the PR #56 tenth review round.

The checked items left behind all belong to groups that are still open --
M-inf horizon buckets and M20 -- and migrate with them. Re-ran the sweep
afterwards: no fully-complete milestone remains in any checklist.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Brings PRs #85 (streaming reports), #86 (GetFullPathNameW) and #87 (release
0.2.1) onto the branch. Thirteen files conflicted; three resolutions were
judgement rather than mechanics, and two of those caught content the merge would
otherwise have silently undone.

**The archive was about to reintroduce the claim PR #86 removed.** This branch
had already archived M22-M29 into COMPLETED-CHECKLIST.md -- the work queued on
`main` an hour ago as `M-inf.2`, so that item is now done rather than pending.
But the archived copy of M26.5 was taken before the correction and still read
"Lexical only", which is exactly the false claim #86 ran to twenty-four review
rounds to remove. Keeping the branch's archive wholesale would have put it back
into the repository by the back door. The archived entry now carries `main`'s
corrected text and a note saying why. Two more sites went the same way: a
`| lexical only |` cell in the root archive's entry table, and D-18 in the
namespace crate's design notes.

**A doc comment `main` shipped broken.** `src/report.rs`'s sink documentation
arrived from peel A missing its opening sentence and with "signatures."
duplicated -- it reads "is arithmetic." with no subject. The conflict surfaced
it because this branch still had the intact sentence. Repaired here with
`main`'s corrected figures (332 sites, 18 signatures) and the branch's opening
clause.

**A claim the merge itself invalidated.** The streaming evidence in DESIGN-NOTES
called `probe-doorbell-cost` "the longest-running probe in this crate at about
0.8 seconds". True when measured on a thirteen-probe crate; false the moment
this merge restored `probe-queue-contention` and its ~65 seconds. The
measurement stands and is unchanged -- it is the stronger case, since a 300 ms
window against 800 ms leaves less room for a slow start to look like buffering
-- so the superlative is qualified rather than the numbers rewritten.

The rest were mechanical: `main`'s re-measured figures win everywhere they
conflict (504 -> 332 sites, ~20 -> 18 signatures, sixteen -> thirteen probes,
`Stop-Process` -> .NET `Process.Kill`), and the branch's side wins where it
describes code only the branch has -- the three probes that bypassed
`emit_report`, and the `fmt::Write` and `json_key` imports their binaries need.
The probes archive is append-only, so it was union-merged and its two timestamped
group headings normalised to the date-only format.

`M2.9` from `main` survives as `M3.1`: M2 is archived on this branch, so the item
would have been swallowed by the archive it postdates. The probes row returns to
PLANS.md because that leaves the crate with open work again.

Verified: 236 + 20 + 11 + 36 + 1 namespace-request tests, 186 probe tests plus 14
ignored and three integration binaries, cargo check --all-targets, clippy
--all-targets, cargo fmt --check, 64 workflow references, encoding over 647
files.
Part of the merge just committed, and separated only because the file
auto-merged and was staged before these edits were made.

This branch archived M26.5 into COMPLETED-CHECKLIST.md while it still read
"Lexical only" -- the claim `GetFullPathNameW` is a pure function of its input,
which PR #86 spent twenty-four review rounds establishing is false: the call
roots against process state, and for a drive-relative path naming another drive
it checks that drive's `=X:` entry against the filesystem and rewrites a rejected
one. Merging the archive unchanged would have reintroduced the corrected-away
claim through a file nobody re-reads.

Two sites, both in the root archive: the M26.5 entry, which now carries `main`'s
corrected text plus a note recording what happened and why the archive was wrong
to keep; and the entry-catalogue table, whose `GetFullPathNameW` row said
`lexical only` in a cell.

The general shape is worth naming, because nothing in the tooling catches it: an
archive is written at the moment work completes, so it captures the
understanding of that moment. When a later branch corrects the understanding, a
merge silently resurrects the old one -- the conflict resolver sees an
append-only file with no textual conflict and takes both sides. Only reading the
archived text against the correction finds it.
Brings in PR #89 (the encoded row is the probe contract) plus the releases
around it. Ten conflicts, and they were not all pick-a-side -- the two branches
developed the report oracle in parallel from a common ancestor that had neither.

**The oracle files take main's version.** This branch carries the early
lineage -- `b7d6c35`, `8020e5e`, `43db54d` -- and main carries seventeen
commits of the same idea carried much further and then rewritten by M3, which
retired the prose oracle outright. Same design (`check` returning
`Vec<Correspondence>`, `assert_corresponds`), main's an order of magnitude
further on. Nothing of this branch's oracle survives that main does not already
supersede.

**`attribution` was a genuine merge, not a side.** This branch added
`measured: Option<&Fingerprint>` (M2.5 -- the banner names the read the body
describes, by construction); main added `renderer_owns_every_line` (a failed
read's `io::Error` can contain a newline, which was giving a six-line
attribution and losing the renderer-owned disclaimer). The two are independent
-- one decides WHICH reading names the body, the other HOW a reading is
contained -- so the merge takes main's body and re-applies the parameter.
Picking either side would have silently dropped a fix.

M2.5's other half had already auto-merged: `measure_observed`, the binary's
call sites, and the unit tests. The integration test came from main, which
still called the two-argument form, and now mirrors the binary as its own doc
says it deliberately does.

**The checklist takes main's numbering.** This branch had renumbered main's
`M2.9` to `M3.1` because M2 was archived here; main kept the M2 numbers
deliberately, because its append-only archive already cites them and
renumbering would leave dangling references in a file that may not be edited to
repair them. Main's `M4`/`M5` structure carries the same item.

**The archive is a union**, both sides appended after the same group and
`COMPLETED-CHECKLIST.md` is append-only: this branch's archived `M2.7`/`M2.8`
are kept ahead of main's four `Moved` groups, leaving the file in date order.

**Three design notes were carried across, and the rest deliberately were not.**
`M2.5`, `M2.7` and `M2.9` exist nowhere on main, so they came over under a
heading that says where they came from and how to read them against M3. This
branch's oracle-era notes did NOT: main already holds them in
`DESIGN-RATIONALE.md`, where M3 relocated them as Tier 2 history, and
re-inserting them here would have put one decision in two tiers at once.

`PLANS.md` keeps this branch's ten-row table (main has six of them) with the
platform-probes row updated to main's text; the crate's own `PLANS.md` gains
the row main added.

Verified: 238 lib tests, 14 integration tests, clippy and fmt clean, the whole
default workspace checks, the default-feature binary build compiles (the
feature-gate arm that no local test reaches), and all eight `sabotage.json`
anchors still resolve.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ependency

`windows-waitable-queues`' closing `] }` and the first line of the `wtf-string`
comment block shared a line:

    ] }# The long-path probe measures a length against `MAX_PATH`, and `MAX_PATH` counts

TOML is content to parse that -- the value ends at `}` and the rest is a comment
-- so it built, tested and shipped CI without complaint. What it cost was
readability in the one direction that matters here: every dependency in this
manifest carries a comment saying why it is depended on, and this one's opening
line read as a trailing note on a DIFFERENT dependency's feature list.

Comment only; no dependency, feature or target changed. Verified rather than
assumed -- `cargo metadata` still reports `experimental-permit-claim, dwcas` on
the queue crate and `wtf-string` still present, all 16 bin targets resolve,
`Cargo.lock` is untouched, and the crate builds (including the default-feature
binary build), checks and tests clean: 238 lib plus 14 integration.

Swept every `Cargo.toml` in the workspace for the same shape -- a value ending
and a comment starting on one line -- and this was the only instance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Six files conflicted. The substantive one is `queue_contention.rs`, which both
sides developed independently: this branch originated it and grew it to 641
lines, while PR #90 branched, reworked it to 1686, and merged. Comparing the two
surfaces, main's is a strict superset -- every item this branch defines exists
there, except `start_barrier`, which PR #90 deliberately replaced with
`StartGate` because a `std::sync::Barrier` cannot be released short of its party
count and a failed spawn deadlocked the probe. Taking main's loses nothing.

`report.rs` and most of the probe's design notes are the same passage after
PR #90's corrections -- withdrawn claims, the regime qualifiers, the
reservation-field-width fix, hard-coded counts removed from prose -- so main's
side wins there too.

Two conflicts needed both sides rather than a choice:

- The probe's `DESIGN-NOTES.md` carries five sections on this branch that main
  never had (`probe-core-affinity`, `probe-peer-index-cache`, the fingerprint,
  the injection seams and its sub-section). They precede the claim-word section,
  whose heading main has rewritten with corrected figures. Kept the five, took
  main's claim-word section, and the other eight regions are main's.
- `PLANS.md`: this branch has ten rows to main's six, and the shared
  `CHECKLIST.md` row diverged -- the branch added M34, main added M30. The merged
  `CHECKLIST.md` contains M19, M20, M21, M34, M37 and M30, so the row now names
  all six. Two other shared rows are further along on this branch (thread-ambient
  has M22-M29 archived; ioring has M1-M19 complete and 0.2.0 shipped), so the
  branch's text stands there.

`Cargo.toml` keeps this branch's three `[[bin]]` entries but takes main's moved
path for the contention probe, and drops the unconditional `dwcas` feature: PR
#90 moved it into `[target.'cfg(...)']` tables precisely so a target without a
native 128-bit exchange still builds, and leaving it here would have undone that.

`src/bin/queue_contention.rs` is deleted. Main renamed it to
`queue_contention/main.rs`; git kept both because this branch had edited the old
path, and the stale copy referenced the pre-rename field names -- the merge did
not compile until it was removed.

Verified: `cargo check --all-targets` and `cargo clippy --all-targets
--all-features` clean, `cargo fmt --check` clean, 310 probe tests pass with both
branches' probes present, encoding check clean over 665 files.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Brings back the `windows-platform-probes` work that was peeled off this branch
as PR #94, after eighteen review rounds corrected it.

**Every conflict resolved in main's favour, and the reason is the same for all
eleven files: main's version is this branch's content plus the corrections.**
The peel took this branch's state of the crate, then fourteen local review
rounds and four rounds on the PR found and fixed defects in it. Nothing in the
branch's side of these files is work main has not seen; it is the pre-correction
form of exactly the same code.

Checked rather than assumed. Every branch-only line in the conflicted files is a
form main has already superseded:

- `Cargo.toml` listed `Win32_System_SystemInformation` twice; main once.
- `DESIGN-NOTES.md` said "Three probes ... branch-local" where main says two,
  `queue_contention` being on main all along; said "Pinning failures panic",
  which main converted to a returned refusal; said "two of its four placement
  rows"; carried arithmetic over a probe list main does not have; and referenced
  an `M34.5` in a root checklist main has no `M34` for.
- `COMPLETED-CHECKLIST.md` linked `src/claim_layout.rs`, a file the same archive
  records deleting, and described `M2.7` as queued when main has done it.
- `core_affinity.rs` had `banner_line()` without reconciliation, the cache-domain
  count over `Observed` rather than `Known`, `confounded` without the
  `CrossCacheCrossClass` requirement, the unsigned verdict-flip test, the bare
  `!` placement marker, and the `slowest`/`fastest` tuples the `NodeRow` and
  `Locality` types replaced.

The remaining files are the same shape.

**Verified by building, not by a clean `git status`.** The previous merge of this
branch produced zero conflict markers and a clean tree while carrying a stale
copy of a renamed binary, and only `cargo check` found it. So: workspace check
and clippy clean, the probes crate's tests pass, `probe-core-affinity` and
`probe-topology` run and render, the encoding check passes over 667 files, the
`check-commit-scope` suite passes on both shells, and `src/bin/queue_contention`
exists only in its directory form with no flat file beside it.
…ists on this branch

The merge from main brought a paragraph saying the gate's own gap is `recorded
and not yet queued` because `no checklist item exists for it`. That is true on
main, which has no `M34`. It is false here: this branch's root CHECKLIST.md
carries `M34.5`, which owns exactly that work -- validate that every workflow
file is well-formed YAML.

The mirror image of the hazard the peel was full of, and worth naming as such: a
statement correct for one lineage and wrong for the other, arriving by merge
rather than by copy. A design note has to name the item that owns the work when
one exists and name the absence when one does not, and which of those is true
depends on the branch. Each side now says what is true of itself, and the
paragraph says so rather than leaving the next merge to rediscover it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… uses

The link landed at the top of a four-hundred-line design note instead of at the
decision it cites. Every other rustdoc-to-markdown link in this workspace carries
its anchor -- \#d-47-detail\, \#d-20\, \#d-43\, \#d-38\,
\#d-streaming-report\ -- and \windows-topology-sys\' own COMPLETED-CHECKLIST
already cites this exact decision as
\[EP-D-2](../topology-planner/DESIGN-NOTES.md#ep-d-2)\. This was the one
instance that dropped it.

Verified the anchor exists in the target and that rustdoc now emits it.

Worth recording what this link does NOT do, since it is easy to assume
otherwise. rustdoc does not copy markdown into \	arget/doc\, so this href
resolves in the source tree -- where it is Ctrl+Clickable, which is the point --
and 404s in rendered documentation. That is true of all eight existing instances
of the pattern on main, so it is a property of the convention rather than of this
link, and \-D rustdoc::broken_intra_doc_links\ cannot see it: a plain markdown
link is not an intra-doc link.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Brings back PR #95, the second peel from this branch, together with the
21 review fixes made to that content after it left. Merge rather than
rebase: this branch is published and 404 commits deep, so a rebase would
rewrite every one of them.

Twelve conflicts, all in planning documents that had been peeled and then
corrected on `main`. Eleven resolved to `main`'s side after checking, for
each file, what this branch held that `main` did not -- in every case the
branch-only lines were the pre-correction wording: the stale "the release
has not happened", the "twelve checked items" tally, the M22-M27 range,
the `Drain`-for-`Claim` trait list, the "has since merged" claim about a
branch that closed unmerged, and 32 doubled apostrophes.

PLANS.md was the exception and took main's version plus this branch's
`crates/topology-planner` row, which `main` does not carry because the
directory does not exist there. That row's own claim was stale too --
"deferred past PR #56" with #56 still open -- and now records that #56
closed unmerged and the component goes in its own pull request.

Three things the conflict list did not name:

- ci.yml carried a conflict resolution from merge `1abcaaf` that had
  welded a step's `if:` and `run:` onto one line, which is not valid
  YAML. `main` never changed the file, so git kept this branch's damaged
  copy silently. Taken from `main`, which is the only difference between
  them.
- The ship checklist's status paragraph said "this branch is the second"
  peel. Written on the peel branch, where that was true; on this branch
  it names the source of the peels. Rewritten to name PR #94 and PR #95.
- windows-platform-probes' design note explained why that paragraph reads
  differently on `main` -- "because `main` has no `M34` for it to belong
  to". The peel moved the root checklist to `main`, so `main` now has
  `M34.5` and both copies say the same thing. The divergence it described
  no longer exists, and `main`'s own copy still asserts it does.

Kept deliberately divergent: windows-topology-sys' COMPLETED-CHECKLIST.md
links topology-planner here and leaves the same references as bare text
on `main`, because the targets resolve on one side and not the other.

Verified: no conflict markers; encoding check clean; no duplicate
work-item IDs; LF throughout; all 14 previously-unresolvable
topology-planner links now resolve, and the only three broken links in
the tree are pre-existing and untouched by this merge. The merge changes
no .rs, .toml or .lock file, so the build is unaffected -- the tree's
code is byte-identical to the pre-merge tip.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 18, 2026 17:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Documentation inconsistencies remain, and the promised waitable-queue implementation is absent.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Documentation-only PR defining the NUMA topology-planner design and updating related planning history; waitable queues are not implemented.

Changes:

  • Adds planner design, component, and checklist documentation.
  • Updates topology and probe cross-references.
  • Revises planning and merged-PR history.
File summaries
File Description
PLANS.md Registers planner work and status.
crates/windows-topology-sys/src/granularity.rs Links proximity documentation to planner requirements.
crates/windows-topology-sys/COMPLETED-CHECKLIST.md Fixes design-session links.
crates/windows-platform-probes/DESIGN-NOTES.md Updates branch-history context.
crates/topology-planner/DESIGN-NOTES.md Records planner design decisions.
crates/topology-planner/COMPONENT.md Defines component scope and architecture.
crates/topology-planner/CHECKLIST.md Tracks planner requirements and milestones.
CHECKLIST-ship-topology-and-queues.md Updates peeled-PR history.
Review details

Suppressed comments (10)

PLANS.md:22

  • This status row is stale: it says M2+/M3+ are parked on the locality session concluding and that the content is still "landing" in peeled pieces, while the same change records that the session has concluded and PR #95 has already merged. Update the row to say the planner remains unlanded and is waiting only on its own re-cut.
| [crates/topology-planner/CHECKLIST.md](crates/topology-planner/CHECKLIST.md) | in progress | **Planned, not built** -- the directory holds a plan and no code, and becomes a crate when M2 begins. Owns the mapping from a stated **goal** plus an abstracted idealized machine description to a set of execution domains: which processors host a domain, where each thread pins, which memory node it allocates from, what channel connects each pair, and where each channel's buffer lives. Filed because that mapping was **unowned**: [CHECKLIST-io-domains.md](CHECKLIST-io-domains.md) M32 lists the contracts "the runtime cannot be written without" and all of them concern the queue, while M33+.1 opens with "one pinned thread, its `IoRing`, its node-local registered pool, its shard" -- presupposing a plan nothing computed. Separate from `windows-topology-sys` because that crate states **facts** and this one applies **policy**; fusing them is what produced `outermost_partitioning_cache`, a policy answer sitting in the facts crate that three consumers then re-derived differently (SH-16.9). M1 was a *requirements* milestone -- it states what the topology must answer, and it fed the locality-model session, which has since concluded as `D-13`..`D-21`. **The component was deferred past PR #56 by direction**, contributing only planning documents there; #56 then closed unmerged on 2026-09-15 and its content is landing in peeled pieces instead, so the component is still unlanded and goes in its own pull request. Per `D-21` the topology reshape lands without it, since `windows-topology-sys` publishes a refined view of what the platform publishes and an adapter absorbs the rest. M2+ and M3+ are parked on that session concluding, and are additionally **awaiting a re-cut**: EP-D-4 and EP-D-5 re-scoped the component into four parts (`topology-model` holding the abstract machine description, the planner's traits and the plan type; `topology-planner`; an inward Windows adapter; an outward realizer), and only M1 has been reconciled with that. EP-1.1 is done and already earned its keep: checking the shard-set query against the model found `Processor::capacity` using `0` as both a valid efficiency class and a "not known" sentinel, which collide on every non-hybrid machine (filed as SH-16.12). | [crates/topology-planner/DESIGN-NOTES.md](crates/topology-planner/DESIGN-NOTES.md), [design-sessions/DESIGN-SESSION-2026-09-02-cache-locality-model.md](design-sessions/DESIGN-SESSION-2026-09-02-cache-locality-model.md) |

crates/topology-planner/CHECKLIST.md:66

  • EP-1.2 is also a long, multi-line completed item. Move its completed body to COMPLETED-CHECKLIST.md and leave only the required one-line stub in the active checklist, consistent with the repository's completed-item rule.
- [x] **EP-1.2** -- **The proximity query, which is the crux.** For an ~~*ordered pair*~~

crates/topology-planner/CHECKLIST.md:87

  • EP-1.3 is a long, multi-line completed item as well. It should be archived in COMPLETED-CHECKLIST.md with an anchor and replaced by a one-line stub here; otherwise the active checklist retains a large historical item after it is complete.
- [x] **EP-1.3** -- **The residency query.** Which memory domain each processor belongs to, and --

crates/topology-planner/CHECKLIST.md:220

  • M-inf is reserved for ungated horizon work, but M-inf.1 explicitly says it needs the static case to exist first. That is a concrete predecessor, so move this item to the appropriate M{n}+ bucket once the static-case milestone is identified, rather than labeling it ungated.
## M-inf: parked, ungated

- [ ] **M-inf.1** -- Re-planning at runtime, when processors are parked, hot-added, or the process
  is given a different CPU-set allocation than it started with. Deliberately not scheduled: it needs

crates/topology-planner/CHECKLIST.md:21

  • This paragraph still says the requirements milestone feeds an open design session, but the later status text in this file says that session has concluded and the questions are recorded as D-13 through D-21. That contradiction makes the current gating state ambiguous.
**Nothing is implemented.** M1 is the only active milestone, and it is deliberately a
*requirements* milestone rather than an implementation one: its output is the concrete statement
of what the model must answer, which the open design session needs in order to settle it.

crates/topology-planner/CHECKLIST.md:21

  • The PR title promises implementing waitable queues, but this change adds no queue implementation and the new component checklist explicitly says "Nothing is implemented." If the queue implementation is not part of this diff, the title should be narrowed; otherwise the implementation is missing from the change set.
**Nothing is implemented.** M1 is the only active milestone, and it is deliberately a
*requirements* milestone rather than an implementation one: its output is the concrete statement
of what the model must answer, which the open design session needs in order to settle it.

crates/topology-planner/CHECKLIST.md:122

  • EP-1.4 is still labelled "BLOCKED" because the model cannot express the state, but the later update in this same item says D-21/M2+.5 made absence representable and that the item no longer blocks the model crate. Since the design session has also concluded, leaving the old blocked rationale unmarked makes this open work look unavailable when it is now the component's own decision.
  **BLOCKED, and not merely because it is downstream.** EP-1.1 through EP-1.3 push requirements
  *into* the model's design, which is why they were worth doing against today's model and found real
  defects in it. This item reads behaviour *out* of the model -- it asks what the planner does when
  the answer is "not observed", a state the model cannot currently express reliably -- so doing it
  now would be analysing a shape that does not exist yet.

crates/topology-planner/CHECKLIST.md:70

  • The checklist says the current model cannot answer proximity and that clients must reconstruct it, but the current windows-topology-sys implementation has MachineMemoryTopology::proximity and the completed M4+.1 item. Please qualify this as the pre-MMT gap or update the item to describe what is still missing after that implementation.
- [x] **EP-1.2** -- **The proximity query, which is the crux.** For an ~~*ordered pair*~~
  **unordered pair** of processors, how close are they -- because that is what chooses SPSC versus
  MPSC versus a routed hop, and it is asked once per pair rather than once per machine. **The
  current model cannot answer it**: `outermost_partitioning_cache` reports one global level and
  `same_cache_domain` reduces it to a boolean at that level, so a client reconstructs the rest and,

crates/topology-planner/DESIGN-NOTES.md:3

  • This new source-component has substantial design history (EP-D-1 through EP-D-5 and a separate locality session), but only adds Tier 1 DESIGN-NOTES.md. Keep the canonical decisions here and add the required Tier 2 rationale/session record for the alternatives and historical reasoning instead of leaving both kinds of content in one file.
# Design notes: the topology planner

Current canonical decisions for this component. See [COMPONENT.md](COMPONENT.md) for what the

crates/topology-planner/DESIGN-NOTES.md:210

  • The current topology model already exposes MachineMemoryTopology::proximity(&[ProcessorId]) and minimal_shared; M4+.1 is recorded as implemented in windows-topology-sys. Thus the statement that today's model answers nothing and has no two-processor query is stale and contradicts the later paragraph that cites that implementation. Mark this as the pre-MMT state or update the section to describe the remaining abstract-model gap.
### What today's model answers: nothing

`MachineMemoryTopology::outermost_partitioning_cache` reports **one level for the whole machine**, and
`Slice::same_cache_domain` reduces that to a boolean at that one level. Neither is pairwise. There
is no query anywhere in `windows-topology-sys` that takes two processors.
  • Files reviewed: 8/8 changed files
  • Comments generated: 11
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread PLANS.md
|---|---|---|---|
| [CHECKLIST-mutation-survivors.md](CHECKLIST-mutation-survivors.md) | not started | Work queued from the workspace-wide cargo-mutants sweep of 2026-09-02, whose findings are kept in [mutation-sweeps/2026-09-02/](mutation-sweeps/2026-09-02/README.md) rather than re-derived -- the run took roughly fourteen hours. 2,792 caught, 1,112 survived, 198 timed out. **The headline numbers mislead in three ways and the README says how**: a timeout in a blocking-API crate is usually a detection that lost its name rather than a gap (measured: one of `windows-waitable-queues`' 120 timeouts fails four tests in 0.00s when re-injected alone), a low score on an executable probe crate is measuring the wrong thing, and three kinds of survivor -- equivalent mutants, unreachable code, and constants that want a `const` assertion -- are not missing tests at all. M1 covers the shipping crates; M2 holds the two crates that are not libraries and whose scope is an engineer's decision; M3 re-runs and prunes rather than hand-editing the tool's output into a second source of truth. | [mutation-sweeps/2026-09-02/README.md](mutation-sweeps/2026-09-02/README.md) |
| [crates/topology-planner/CHECKLIST.md](crates/topology-planner/CHECKLIST.md) | in progress | **Planned, not built** -- the directory holds a plan and no code, and becomes a crate when M2 begins. Owns the mapping from a stated **goal** plus an abstracted idealized machine description to a set of execution domains: which processors host a domain, where each thread pins, which memory node it allocates from, what channel connects each pair, and where each channel's buffer lives. Filed because that mapping was **unowned**: [CHECKLIST-io-domains.md](CHECKLIST-io-domains.md) M32 lists the contracts "the runtime cannot be written without" and all of them concern the queue, while M33+.1 opens with "one pinned thread, its `IoRing`, its node-local registered pool, its shard" -- presupposing a plan nothing computed. Separate from `windows-topology-sys` because that crate states **facts** and this one applies **policy**; fusing them is what produced `outermost_partitioning_cache`, a policy answer sitting in the facts crate that three consumers then re-derived differently (SH-16.9). M1 was a *requirements* milestone -- it states what the topology must answer, and it fed the locality-model session, which has since concluded as `D-13`..`D-21`. **The component was deferred past PR #56 by direction**, contributing only planning documents there; #56 then closed unmerged on 2026-09-15 and its content is landing in peeled pieces instead, so the component is still unlanded and goes in its own pull request. Per `D-21` the topology reshape lands without it, since `windows-topology-sys` publishes a refined view of what the platform publishes and an adapter absorbs the rest. M2+ and M3+ are parked on that session concluding, and are additionally **awaiting a re-cut**: EP-D-4 and EP-D-5 re-scoped the component into four parts (`topology-model` holding the abstract machine description, the planner's traits and the plan type; `topology-planner`; an inward Windows adapter; an outward realizer), and only M1 has been reconciled with that. EP-1.1 is done and already earned its keep: checking the shard-set query against the model found `Processor::capacity` using `0` as both a valid efficiency class and a "not known" sentinel, which collide on every non-hybrid machine (filed as SH-16.12). | [crates/topology-planner/DESIGN-NOTES.md](crates/topology-planner/DESIGN-NOTES.md), [design-sessions/DESIGN-SESSION-2026-09-02-cache-locality-model.md](design-sessions/DESIGN-SESSION-2026-09-02-cache-locality-model.md) |
| [CHECKLIST-io-domains.md](CHECKLIST-io-domains.md) | in progress | **M30 is complete and archived** in [COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md): the queue crate's name, skeleton and SPSC shape. M31 built the bounded-array MPSC (with a lazily created manual-reset doorbell whose reset cannot be separated from the observation that there is nothing to take -- achieved by ordering plus a re-check rather than by a lock, per D-9 and D-15) and is done but for `M31.6`, the `loom` verification, which is re-homed as `M30.4` in [CHECKLIST.md](CHECKLIST.md). M32 remains: the contract decisions -- ordering, correlation, backpressure among them -- the domain runtime cannot be written without. M33+ parks the runtime itself, the creation-time-affinity thread builder, the namespace `Outcome` extension, the client-side `ThreadpoolWait` fan-in helper, and the durability crate. M-inf holds items each gated on a specific measurement rather than on taste. The N=1 path is the whole first deliverable and depends on no NUMA hardware. | [DESIGN-NOTES.md](DESIGN-NOTES.md), [DESIGN-SESSION-2026-08-30-numa-sharded-io-execution-domains.md](design-sessions/DESIGN-SESSION-2026-08-30-numa-sharded-io-execution-domains.md) |
@@ -0,0 +1,221 @@
# Checklist: the topology planner
of what the model must answer, which the open design session needs in order to settle it.

**Deferred past PR #56, by direction.** This component contributes only planning documents to that
PR and no code. `windows-topology-sys`'s reshape lands there without it: per

| Milestone | State | What it is waiting on |
|---|---|---|
| M1 the input contract | 4 done, 1 open | `EP-1.5`'s coverage half, which wants a settled model |
|---|---|---|
| M1 the input contract | 4 done, 1 open | `EP-1.5`'s coverage half, which wants a settled model |
| M1+ scenario and naming | **partly answered** | the name is settled (EP-D-4); the goal input is deferred for litigation, by direction |
| M2+ the plan as a value | parked, **and needs re-cutting** | re-cut against EP-D-4/EP-D-5, then the topology reshape landing |
Comment on lines +87 to +92
- [x] **EP-1.3** -- **The residency query.** Which memory domain each processor belongs to, and --
for a pair spanning two of them -- what it costs to place a shared buffer on one side rather than
the other. **Gap already identified:** `MachineMemoryTopology::distances` exists, is never populated, and Win32
cannot populate it; the measurement exists in `windows-placement-probe` and reaches nothing.
Tracked as `SH-16.11`. The probe measures this per node pair with a dedicated ring-placement
column precisely because it was found to matter.
Comment on lines +156 to +159
> **-> CROSS-COMPONENT HANDOFF:** next work is in the repository root ->
> [DESIGN-SESSION-2026-09-02-cache-locality-model.md](../../design-sessions/DESIGN-SESSION-2026-09-02-cache-locality-model.md)
> -> `SH-16.8` in
> [CHECKLIST-ship-topology-and-queues.md](../../CHECKLIST-ship-topology-and-queues.md).
Comment on lines +109 to +110
documents to that PR and no code. The topology reshape it fed requirements into is landing there
without it, because [D-21](../windows-topology-sys/DESIGN-NOTES.md#d-21) establishes that
M2 and M4 build. What remains here is this component's own work, not a wait on someone else's.

The name is settled; the crate does not exist yet. It is deliberately absent from
`release-please-config.json`, the publish workflow's tag patterns, and the workspace manifest until
Comment on lines +262 to +265
`MachineMemoryTopology::distances` exists, and it is easy to read its permanent `None` as an oversight. It is
not. The field is documented as being for a fed-in description, because "Windows exposes no
user-mode SLIT reader", and that is accurate.

@MikeGrier MikeGrier closed this Sep 18, 2026
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.

2 participants