windows-namespace-request-sys 0.4.1

Owned, marshalable parameter sets for synchronous Win32 namespace calls.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
# Design rationale: windows-namespace-request-sys

Tier 2. How the decisions in [DESIGN-NOTES.md](DESIGN-NOTES.md) were reached --
alternatives considered, drafts that were wrong, and the reasoning that got
discarded along the way. Consulted for "why", never for "what is true now": if
this file and Tier 1 disagree, **Tier 1 wins**.

Cross-referenced by decision ID.

## D-18: `GetFullPathNameW` is not lexical

The decision is in [DESIGN-NOTES.md](DESIGN-NOTES.md) -> `D-18`. What follows is
the record of getting there, which is unusually worth keeping because the same
mistake recurred in nine successive wordings, enumerated below.

### The shape of the error, which never changed

Every wrong draft did the same thing: **named a mechanism the evidence did not
reach.** The subject moved -- what the call does, what a number measures, which
alternatives cost less -- but the failure did not. That is why the decision
records the constraint rather than only the conclusion: a reader who takes only
"it is not lexical" away from D-18 has the answer without the thing that keeps
producing wrong answers.

### The drafts

1. **"This call is lexical."** The original text, contradicted by its own next
   sentence, which said it resolves against the process current directory. A
   lexical canonicalizer is a pure function of its input; this reads mutable
   process state.

2. **"It resolves relative components and `.`/`..` against the process current
   directory."** The first correction, which overshot. Collapsing `.`/`..` has
   an output that is a function of the input alone -- `C:\a\..\b` becomes
   `C:\b` under any current directory, and whether or not `C:\a` exists.
   Measured under two different current directories.

   **This entry itself carried the error it describes, for twenty-three
   rounds.** It read "pure string work and reads no process state at all", and
   two different current directories giving the same answer shows the output
   does not depend on that state, not that nothing was read. Every correction in
   this file was aimed at the *rooting* half; the overreach in the **lexical**
   half was the sentence doing the correcting, which is why nobody looked at it.
   Whether rooting reads process state is a separate question, and it is
   answered observably: changing the current directory changes the result.

3. **"The probe measures roughly 212 ns per resolution."** It does not. The
   probe reports a construct-and-drop cycle whose total contains an allocation
   and a drop, and its own output says so.

4. **"The probe deliberately declines to decompose that total."** An
   over-correction of (3). It does decompose: the report states that recycling
   an already-resolved path pays ~42 ns against ~210 ns and attributes the
   difference to the resolution. What the probe withholds is the **mechanism** --
   whether any of it is a kernel transition -- not the division.

5. **"Roughly 168 ns is this call's measured share."** Taking (4)'s split at face
   value. The subtraction spans the whole preparation step, which makes two heap
   allocations of the crate's own against the clone's one, plus a builder chain.
   Timed directly with no allocation in the loop, the call is about 110 ns --
   the draft overstated it by roughly half.

6. **"Either alternative is cheaper."** Said of `PathCchCanonicalizeEx` and
   `PathAllocCanonicalize` from the first commit onwards, and never measured.
   Nothing in this repository benchmarks them, Microsoft documents behaviour
   rather than relative cost, and `PathAllocCanonicalize` allocates its own
   result. Removed rather than substantiated, because the decision rests on
   rooting semantics and never needed it.

7. **"Touches no filesystem."** The claim that survived longest, because it is
   nearly right. What Microsoft documents is that the function does not verify
   that the resulting path is valid or names an existing file -- a statement
   about *verification*, not about I/O.

8. **"Observation cannot close that gap."** The correction to (7), and wrong in
   a more interesting way: observation *did* close it, in the affirmative
   direction, the moment anyone looked at the right form. Resolving `X:foo` for
   a non-current drive distinguishes an existing directory from an existing
   *file* from a missing one, and rewrites the `=X:` entry when it is not a
   directory. That is a filesystem query and a write to the process environment,
   in a call four drafts had described as reading process memory.

   The reasoning behind those drafts was sound -- the current directory is in
   the PEB, the `=X:` variables are in the environment block, both are process
   memory -- and the conclusion was false. It is the cleanest example in this
   file of the standing failure: a mechanism argued from where the data lives
   rather than measured. Every earlier draft at least *knew* it was asserting a
   mechanism; this one thought it was declining to.

9. **"A filesystem query rather than a syntax test."** Written in the paragraph
   correcting (8), and wrong the same way within a single round. Having found
   that an entry naming a missing directory or an existing *file* is rejected,
   the draft concluded the gate was existence and not shape. Measured, shape
   gates it too and independently: `C:/Windows/System32`,
   `C:\Windows\System32\.`, `C:\Windows\System32\..\System32` and
   `\\?\C:\Windows\System32` are each rejected while naming the same existing
   directory that `C:\Windows\System32` is accepted for. Acceptance is also
   literal -- `C:\Windows\` yields `C:\Windows\\foo`, with no normalisation at
   the join.

   The tell was the word *rather*. Ruling an alternative out is a strictly
   stronger claim than establishing the one you measured, and needs its own
   evidence; three observations of the existence check said nothing about
   shape. Both halves are now pinned by
   `a_rejected_drive_entry_is_replaced_by_the_drive_root` so the next draft
   cannot restate this from memory.

### Two facts that were measured, then asserted too narrowly

Both were found by review after the correction had already shipped, and both
were *enumerations* -- which is the form this kind of error likes.

- **The rooting forms.** Stated first as one case (the current directory), then
  two, and finally three: a relative path takes the current directory, a
  root-relative path takes only that directory's *root* (which is
  `\\server\share\` under a UNC current directory, so "current drive" was
  wrong), and a drive-relative path naming a drive OTHER than the current one
  takes the entry recorded for that drive from `=C:`, while on the current drive
  that entry makes no difference and the process directory wins. The
  current-drive arm is a fourth correction to the same enumeration, found the
  same way as the first three and after them: the count "three forms" was itself
  one of the things stated more confidently than measured.

- **The device set.** The short-circuit was first described as "exact-match
  only", which `CON:` disproves; then enumerated as `CON`/`NUL`/`PRN`/`AUX`/
  `COM1`-`9`/`LPT1`-`9`/`CONIN$`/`CONOUT$`, which omits the superscript
  spellings `COM\u{00b9}`, `COM\u{00b2}`, `COM\u{00b3}` and their `LPT`
  equivalents. Those are exactly the members a hand-written denylist misses, and
  the documentation asserted a closed list without them until a review measured
  it. The tests in [tests.rs]src/full_path/tests.rs now pin every documented
  spelling so the next omission fails CI instead of a review -- each positive
  spelling against the device path it produces, and each negative one against
  its full rooted result.

  **A third correction, and the one that shows why "the device set" was the
  wrong unit all along.** A review asked for the documented `\CON` case to be
  pinned, since the doc stated it and no test covered it. Writing that
  assertion as the general rule the request was phrased in -- "a root-relative
  device word roots at the current directory's root" -- would have pinned a
  false claim. Measured across all eight accepted names, `NUL` alone
  short-circuits as the final component of ANY path: `\NUL`, `.\NUL`, `a\NUL`
  and `C:\NUL` all reach `\\.\NUL`, while every `CON`, `PRN`, `AUX`, `CONIN$`,
  `CONOUT$`, `COM1` and `LPT1` spelling roots. So a fully qualified path can
  still name a device.

  Every earlier entry here is a claim generalised from too few observations.
  This one was generalised from one MEMBER to a set, which is the same error in
  a dimension nobody had checked -- the enumeration of the set was audited
  twice, and the assumption that its members behave alike never was.

  That distinction between kinds of pinning was itself a later correction, and
  it is the reason the sentence now says which KIND of pinning each case gets. The claim "pins every
  documented spelling" was written while one negative spelling, `CON:x`, was
  covered only by `!starts_with("\\.\\")` -- a predicate the unrooted literal
  satisfies. So the claim was true of the list and false of the strength, which
  is the same shape as the trimming generalisation two entries below.

### The measurement that was itself unmeasured

The sharpest instance in this whole sequence is not about `GetFullPathNameW` at
all. The test helper that reads a `=X:` entry folded "empty value" and "absent
name" into one answer, and said so in a comment that called the equivalence
*measured*: `SetEnvironmentVariableW(name, "")` was reported to succeed and then
read back exactly as a name that was never set.

It does not. `GetEnvironmentVariableW` returns `0` for both, and the last error
is the only thing that separates them -- so a measurement that never cleared the
last error first could read nothing but whatever an earlier call had left there.
Cleared and re-measured, the two are distinct, for an ordinary name and an `=X:`
name alike:

| state | return | last error |
|---|---|---|
| set to `""` | `0` | `ERROR_SUCCESS` |
| deleted | `0` | `ERROR_ENVVAR_NOT_FOUND` |

An earlier note also recorded that `ERROR_ENVVAR_NOT_FOUND` "never surfaced even
for genuinely absent variables", which has the same cause and one more: the
deletion under test had not happened, because the null that deletes an entry had
been marshalled as an empty string instead. Two layers of the harness agreeing
with each other is not a measurement.

The consequence was live rather than cosmetic. `BorrowedDriveEntry` restores a
borrowed entry on unwind precisely so a panicking test cannot leak process
state; with the two answers collapsed, restoring an inherited *empty* entry
**deleted** it. The guard destroyed the state it existed to preserve, in exactly
one case, and only that case.

Pinned by `an_empty_drive_entry_is_distinguished_from_an_absent_one` in
[drive_entry.rs](src/full_path/tests/drive_entry.rs), and verified by re-introducing the collapse,
which makes it fail.

The general form is worth keeping separately from the specific fact: **a
comment that says "measured" is a claim about a procedure, and the procedure can
be wrong in ways the result never reveals.** Every other entry in this file is an
assertion that outran its evidence. This one had evidence, and the evidence was
of something else.

### The sweep, and its arithmetic

The wrong word had spread well beyond where it was reported. The consuming
probe's checklist item named [full_path.rs](src/full_path.rs) only; that file
held **three of the nine** on its own -- the module doc and both doc examples --
with two more in [path.rs](src/path.rs), two in
[tests.rs](src/full_path/tests.rs), one in an acceptance comment and one in
[DESIGN-NOTES.md](DESIGN-NOTES.md). Nine across five files, from a report naming
one. The reported site was a sample, not the population.

### Why the alternatives were never seriously in contention

`PathCchCanonicalizeEx` and `PathAllocCanonicalize` canonicalize without
rooting, which sounds like a strictly smaller job and therefore an easy win. It
is the wrong trade for this crate: a path that is still relative has its meaning
settled on the worker thread at execution time, against a current directory any
thread may have changed in between. That is precisely the race preparation
exists to close, so the "smaller job" omits the part being bought.

Recorded here rather than only in Tier 1 so the next reader who notices the
cheaper-looking API does not have to re-derive why it was declined.

## D-19: grafting a relative path

The decision is in [DESIGN-NOTES.md](DESIGN-NOTES.md) -> `D-19`. This is the record of how it was
reached, with the measurements behind it. They were taken on 2026-10-15 on Windows 11 with a probe
test that was not kept (it failed on purpose so libtest would replay its output), the process
current directory set to `C:\Windows\System32`, the long-path flags below not including a
long-path-aware host.

**How the direction arrived.** The engineer asked what `prepare` did that was not Win32's own, said
a path should reach Win32 untouched with no length limit and no checking of its own, and then, once
"resolve at build time" was read as "when the object is built" and not at compile time, held that the
need to capture the root at construction remained, with relative paths resolved textually against it
later. Windows provides a lexical join, `PathAllocCombine`, which [D-18](DESIGN-NOTES.md#d-18) had not
weighed (it weighed only the canonicalisers), so it was measured.

**`PathAllocCombine` against `GetFullPathNameW`** (base = the process current directory):

| Input | `PathAllocCombine`, flags 0 | `GetFullPathNameW` |
|---|---|---|
| `a\b`, `.\a`, `..\a`, `a\..\b`, `..\..\..\..\x` | the same | the same |
| `\a\b` | `C:\a\b` | `C:\a\b` |
| `a.`, `a ` | `...\a` | `...\a` |
| `a/b` | `...\a/b` (slash kept) | `...\a\b` |
| `C:a`, `C:a\b`, `Q:a` | `C:a`, `C:a\b`, `Q:a` (unresolved) | resolved |
| `C:` | `C:\` | `C:\Windows\System32` |
| `C:\x\..\y` | `C:\y` | `C:\y` |
| `C:/x/y` | `C:/x/y` | `C:\x\y` |
| `\\?\C:\x\.\y` | `C:\x\y` (prefix dropped, `.` collapsed) | `\\?\C:\x\y` |
| `\\.\PhysicalDrive0` | `\\PhysicalDrive0` | `\\.\PhysicalDrive0` |
| `\\.\C:\x\..\y` | `\\C:\y` | `\\.\C:\y` |
| `CON`, `NUL`, `COM1` | `...\System32\CON` and so on | `\\.\CON` and so on |
| empty | the base | error 87 |

With a UNC base `\\server\share\dir\sub`: `a\b` and `\a\b` join correctly, and `..\..\..\x` gives
`\\server\x`, past the share root. With a verbatim base `\\?\C:\work\proj` the result loses its
prefix. Long results (a base of about 350 units): flags 0 fail with `0x800700ce`;
`PATHCCH_ALLOW_LONG_PATHS` gives a `\\?\` path; adding
`PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS` gives a plain long path. `PATHCCH_DO_NOT_NORMALIZE_SEGMENTS`
keeps a trailing dot or space that the default strips.

**`CreateFileW` on a device name, spelled four ways** (open for query; the number is the Win32 error,
or the file type):

| Name | bare | joined onto the directory | `x\name`, `x` missing | `drivers\name`, `drivers` exists |
|---|---|---|---|---|
| `NUL` | char device | char device | 3 | char device |
| `CON`, `con` | 87 | 2 | 3 | 2 |
| `CONIN$`, `CONOUT$` | char device | 2 | 3 | 2 |
| `PRN`, `AUX`, `COM1`, `COM2`, `LPT1` | 2 | 2 | 3 | 2 |
| `NUL.txt` | 2 | 2 | 3 | 2 |

So `NUL` is a device in any position and a join is faithful to it, where `GetFullPathNameW`'s habit of
resolving `a\NUL` to `\\.\NUL` is not what `CreateFileW` does with `a\NUL` (it fails with error 3 when
`a` is missing). `CON`, `CONIN$` and `CONOUT$` change meaning under a join, which is rule 5 of the
decision. `PRN`, `AUX`, `COM1`, `COM2` and `LPT1` fail the same way in every spelling here, so nothing
is shown about them either way.

**What was weighed and set aside.** A combine call with the long-path flags, for the reasons in the
decision. A classification that grafts everything but the forms known to be wrong, which would make
the list of wrong forms the policy; the engineer's rule was that the graft is allowed "when we can
show that the child path is clearly safe", so the rule names what is safe and lets the rest through
untouched.

## D-20: one overlapped-safe `DeviceIoControl` helper

The decision is in [DESIGN-NOTES.md](DESIGN-NOTES.md) -> `D-20`. This is how it was reached.

**The problem was visible before any code.** The crate's handle-taking entries (`QueryVolumeInformation`
and the rest) call functions that take no `OVERLAPPED`, so how a handle was opened never mattered.
`DeviceIoControl` is the first call that does take one, and the same crate that cannot see how a
handle was opened is the one that has to call it.

**What was weighed.**

- **Ask the handle how it was opened.** The only route to the overlapped flag is an `Nt*` query,
  which the repository's DOCUMENTED APIS ONLY rule forbids, and
  `GetFileInformationByHandleEx` does not report it.
- **Pass a null `OVERLAPPED`.** Documented to fail unpredictably on an overlapped handle, and
  unpredictable is not an outcome a catalogue entry can report.
- **Associate a port of the helper's own, or change the handle's completion-notification mode.**
  Either changes state on a handle the caller owns and shares; a request borrows a duplicate, not
  the right to configure it.
- **Give every call an event and wait for it.** What was chosen. It is the same call on both kinds of
  handle, and it costs an event creation per call, which is the price of not knowing.
- **The event's low bit.** Without it a handle on a completion port gets a packet when the call
  completes, which reaches a consumer's loop as a completion it did not ask for. A sabotage that
  removes the bit is caught by a test that associates the handle with a port and checks nothing
  is queued after the call.

**Why the tests use two controls.** `FSCTL_QUERY_VOLUME_NUMA_INFO` is the entry's own control and the
one whose buffer-size failure is documented, but a volume may have no answer, so tests on it stand
down when there is none. The first sabotage sweep showed what that costs: telling the helper the
buffer was empty turned every call into a failure, and every test that tolerated "no answer" passed.
`FSCTL_GET_COMPRESSION` is answered by every file on an NTFS or ReFS volume, so the helper's tests
assert success on it and do not stand down; that closed the hole.

**How the pending path came to be pinned.** A named-pipe listen (`FSCTL_PIPE_LISTEN`) was the obvious
control that pends until a client connects. It failed with `ERROR_INVALID_FUNCTION`, so it was removed
rather than worked around. The first sabotage entries for the pending path were then caught only
because the full suite happened to make a call pend, and one survived for the same reason; a throwaway
test that counted `ERROR_IO_PENDING` over 20000 calls with the pending branch disabled showed 0, so
those entries were removed as neither a control nor a declared blind spot. The engineer's question
("I guess add a sabotage for it?") then led to the seam: split the helper at the one call that makes it a
control, and drive the rest with a call that does pend on demand.

**What was weighed for that call.** A fake that sets `OVERLAPPED.Internal` and `InternalHigh` and
signals the event by hand would pend deterministically too, but it relies on fields the documentation
reserves for the system and on how `GetOverlappedResult` reads them, which is the incidental-behaviour
binding the platform rules forbid. An overlapped `ReadFile` on a pipe uses only documented behaviour.

**A defect the seam work found in the tests.** The helper's tests opened handles without taking the read
side of `handle_allocation`, so on roughly one run in thirty another test, which holds the write side
while it reasons about a closed handle's value, saw a handle reused and panicked, poisoning the lock for
every test that follows. Measured as 1 mass failure in 60 runs before the fix and none in 200 after.
Separately, two `watch` tests shared the fixture label `watch-defaults` and removed each other's
directory, failing 8 of 30 runs; each now has its own.

**What this did not settle at the time.** `DeviceIoControl`'s own behaviour when it pends was unchecked, for
want of a control that does so on demand. `FSCTL_REQUEST_OPLOCK` is one, found in `NR-2.2`
([D-21](#d-21)'s rationale below).

## D-21: the disk-extents entry

The decision is in [DESIGN-NOTES.md](DESIGN-NOTES.md) -> `D-21`. This is how it was reached.

**A premise in the checklist was wrong.** `NR-2.2` was written as "first, give the helper an input buffer, since
this item and the next two need one". This control takes none; `NR-2.3` and `NR-2.4`
(`IOCTL_STORAGE_QUERY_PROPERTY`) do. The input buffer was done under `NR-2.2` anyway, because it had been
scheduled there and because the one control found that pends on demand, `FSCTL_REQUEST_OPLOCK`, takes input, so
the two were the same piece of work; but the entry itself did not need it.

**Finding the control that pends.** The candidate was `FSCTL_REQUEST_OPLOCK`. A throwaway test requested a read
oplock on an overlapped handle and had another thread open the file for write: the call pended and did not
return in 25 seconds, so it was killed by a timer outside the test (an in-test wait would have hung the run). The
same test with the other thread writing a byte completed in 304 ms against a 300 ms delay, with the oplock's
original level `1` and new level `0`. So a write-open does not break a read oplock and a write does, and the
control pends exactly as long as the test says. The real test uses that, and the same request with no input,
which the device refuses at once, is what shows the input is what made it work.

**Why a `Vec<DiskExtent>` of `u64`s and not the raw reply.** A consumer given the bytes would have to know the
layout, the `ERROR_MORE_DATA` convention and where the count is, which is what an entry exists to carry. `u64`
is what an offset and a length in bytes are; the structure's `LARGE_INTEGER` is signed, and a negative value is
not a length, so it is reported as malformed and not cast. `i64`s that a caller then casts would put the same
check in every caller. `AlignedBuf` was not used: the reply is read with `from_ne_bytes`, so no alignment is
needed, and a `Vec<u8>` allocated with `try_reserve_exact` fails as an error the caller receives.

**The fake device had the wrong code, and the real one showed it.** The documentation suggests
`ERROR_INSUFFICIENT_BUFFER` for a buffer smaller than the header, and the first fake and the first real-device
test both said so. The real-device test failed (87, not 122), a sweep of every size from 0 to 48 found the
boundary (below the count plus one extent: 87; from it up: the answer), and the fake was corrected to match. The
fake is the only way to test the multi-extent half on this machine, so its one measured behaviour is stated where
it is built, and the documented half (`ERROR_MORE_DATA` with the count) is stated as not observed.

**What was weighed and set aside.** Sharing `win-volume-sys`'s `extents.rs` instead of writing this: that crate
is a working, released one, and the rule for speculative duplicate work is to build beside it and decide when the
new one is proven, so the decision is `NR-2.6`. Capping the count the device reports, to refuse a huge
allocation: a cap is a number this crate would have to choose and defend, the count comes from the volume driver,
and the failure is already an error the caller receives; not decided against for ever, but nothing shows it is
needed.

## D-22: storage properties

The decision is in [DESIGN-NOTES.md](DESIGN-NOTES.md) -> `D-22`. This is how it was reached.

**The `NR-2.3` "check first" was the first question, and it was cheap.** The checklist said to check that an
`IOCTL_STORAGE_*` code behaves through the helper before building on it, because `FSCTL_PIPE_LISTEN` had not.
A throwaway test opened the disk with no access and asked for the property with output buffers of 0, 4, 7, 8,
16, 20, 24, 64 and 4096 bytes and with inputs shorter than the structure. That one run gave the whole size
protocol (7: bad length; 8: the header alone; 9 up: more data with the header written; 4096: the reply) and
the reply's shape, and a second probe printing only numeric fields showed the descriptor's own size equals what
remains of the reply. Nothing in the reply was recorded beyond those facts: it holds the device's identifiers,
which identify the machine.

**What was weighed for the reply type.**

- *Parsed, with typed identifiers inside the descriptors.* Declined for now, which the engineer confirmed
  ("at this time, no. We can revisit later if necessary"), for the gradient reason: which identifiers a key
  uses is the locality helper's question, which has not been asked yet, and a typed surface here would be an
  answer to it.
- *Raw bytes of the whole reply only.* A consumer would redo the offset checks every time, and the checks are
  where a damaged reply becomes an out-of-bounds read; so the sections are delimited and bounds-checked here and
  handed back as slices.
- *One generic entry that takes a property id and returns bytes.* Raised with the engineer, who said yes; built
  as `QueryStorageProperty` (below). Nothing argued it had no value, and it lets a consumer reach a property
  this crate has no entry for.

**Why the zeroed-buffer and `u32` reader moved.** The second entry to size a buffer from a reply needed the
same two helpers `volume_extents` had written privately; the mono-repo rule is to fix the layer, so they are now
in the helper's module and `volume_extents` uses them. Its sabotage entry for the allocation moved with the
function.

**The sweep and the redundant check.** The first sweep caught all twenty-one entries, but only after a pass over
the survivors a mutant could hide behind: a length guard in `decode` was refused anyway by the first offset read
that fell outside the header, so mutating it changed no outcome, and it was deleted rather than defended; and the
tests for a call that claims more bytes than it was given room for would have passed with the claim check
removed, because the header the fake wrote was empty, so they now write a valid one, and the test for a reply
shorter than its header asserts the number of calls, which is what distinguishes refusing it from asking again.

**A name collision, and what it showed.** The oplock test in `device_control` had its own `u32_at` that panicked
on a short buffer; the shared one returns `None`. The test now uses the shared one, so the one reader every entry
relies on is the one the earliest tests exercise.

## D-22 addendum: the generic storage property query

**What a generic entry exposes that a typed one hid.** A throwaway probe asked the disk about the cases the
unique-id entry never reached. An exists query on a real property succeeds with nothing written; an unknown
property fails (`ERROR_INVALID_FUNCTION`) for exists and standard alike; a mask query and an undefined query
type fail with `ERROR_NOT_SUPPORTED`. Those shaped the entry: an exists query cannot go through the
learn-the-size loop, which would refuse an empty reply as shorter than its header, so it is its own single call.

**A wrong conclusion that a second probe corrected.** The first probe sent additional parameters of zero bytes
and got the same reply for every length, and the module documentation was written to say a property that takes
none accepts and ignores them (measured). The test written from it failed on a nonzero byte: the unique-id
property refuses 0xA5 with `ERROR_INVALID_PARAMETER`. A second probe over values and lengths showed the first
additional byte is read by that property (0 gives 112 bytes, 1 gives 140) and ignored by the device property.
The claim was withdrawn from both modules before anything was committed, the test now covers both properties,
and the byte's effect on the answer is what shows additional parameters arriving at a real device. The cause was
a probe whose input could not distinguish "ignored" from "zero"; it is recorded because the same shape of probe
would have been trusted again.

**A lead, not an interpretation.** The larger reply for a first byte of 1 may carry more identifiers than the
usual one. The typed entry sends no parameters, so it does not ask. The generic entry is how a consumer gets the
larger reply, and what the byte means is not recorded as anything but an observation. If the locality helper
needs those identifiers, that is the moment to ask what the byte selects and whether the typed entry should
offer it.

**Why `Arc<[u8]>` for the parameters.** A request's `try_clone` returns a handle-capture error and has no
allocation-failure case, and the crate's rule is that an allocation that fails is an error the caller receives.
Sharing the parameters makes the copy allocation-free, so the rule holds without a new error variant. The
input that is sent is built fallibly (`zeroed_output`), at `perform`, where an error can be returned.

**Newtypes over the raw values.** Both the property id and the query type are `i32` in Windows, and a call that
swaps them compiles and asks a different question. Newtypes make the swap a compile error, and `from_raw` keeps
every value Windows may define later reachable, so the types narrow nothing. Only the query type has named
constants, because the three documented values are all that enumeration has; the property ids are `windows-sys`'
and are not reproduced.

## D-23: a disk's NUMA node

The decision is in [DESIGN-NOTES.md](DESIGN-NOTES.md) -> `D-23`. This is how it was reached.

**The documentation check came first, and found less than the other entries had.** Looking up the structure's
reference page returned a 404, and the enumeration page's row for `StorageDeviceNumaProperty` is one sentence
that names no structure, where the unique-id row names its structure and points at the driver kit. `windows-sys`
carries `STORAGE_DEVICE_NUMA_PROPERTY` from the SDK header, so the layout is public, but "public header" and
"documented" are not the same, and the repository's rule is about the second. The decision to proceed on the
header's footing is recorded in `D-23` as one the engineer can overrule, with the generic entry as the way
around it, rather than as settled: the entry was asked for, the property is documented, and the structure is the
one the property's own header ships, but the weaker footing is a fact the engineer should have in front of them.

**One probe, which found the thing worth recording.** A throwaway test asked the property with output buffers of
0, 7, 8, 11, 12, 13, 64 and 512 bytes, for the standard and the exists query. The standard answers were the whole
size protocol and they differed from the unique-id property's: `ERROR_INSUFFICIENT_BUFFER` below the header, and
a success with the header alone between the header and the reply. The `storage_property` module documentation
had stated the unique-id behaviour as the call's, with "(measured)" on it. The loop was already correct because
it reads the size from the header of whichever comes back, but the documentation would have misled whoever added
the next property, so it was rewritten as a table with one row per property measured, and a test pins this
property's row.

**What was weighed.**

- *A single entry for both NUMA questions* (volume and disk). Rejected: they are different calls on different
  handles, and the point of asking per disk is that a volume's answer is not every disk's.
- *Reporting `u32::MAX` as `None`.* Rejected for the reason `QueryVolumeNumaInfo` gave: it would be a sentinel
  this crate chose, and nothing documented says it means none.
- *A test comparing the disk's node with the volume's.* Written, and removed: it printed two numbers and compared
  nothing, and on a one-node machine nothing would distinguish a correct comparison from a wrong one. A test
  that cannot fail is not a test.

**Why the descriptor tests use constructed replies.** A one-node machine reports zero, which is also what a field
read from the wrong place would give when the bytes are zeros. So the field-placement tests build replies with
distinct values in each field, and the sabotages for a node read from the wrong field are caught by those, not by
the real disk.

## D-24: the dependency query

The decision is in [DESIGN-NOTES.md](DESIGN-NOTES.md) -> `D-24`. This is how it was reached.

**What the probes showed that the item did not say.** The item asked for the call and for a statement of what
an unelevated run can reach. Probing it with a buffer whose version field was 0 or 3 gave
`ERROR_INVALID_LEVEL`, and with 1 or 2 it succeeded, so the version is an input the caller sets. Sizes below 72
were `ERROR_INVALID_PARAMETER` and not the `ERROR_INSUFFICIENT_BUFFER` that a size-learning loop looks for, so
a loop that started small would have been refused instead of told a size. The raised first size follows from
that.

**The code `0xC03A0015` looked foreign and was not.** `HOST_VOLUMES` on a volume that is not virtual returned
3225026581, and the first reading was that no Win32 constant names a value that large, so it was recorded as an
`NTSTATUS`. That was wrong: `ERROR_VIRTDISK_NOT_VIRTUAL_DISK` is that value, and the virtdisk errors are defined
in that range. The reading was formed without searching `windows-sys` for the number, which is the check that
would have settled it. It is carried as the call returned it, and the test compares against the named constant.

**Names, and the choice not to trust the buffer.** The structure's name fields are pointers. Reading them
directly would have been simplest and would have trusted a reply this machine could never produce, so each
pointer is converted to an offset against the reply's own address and bounds-checked, and the string must end
inside the bytes the call said it used. Both checks matter independently: a pointer exactly at the end of the
reply is refused for lying outside it, and a string that reaches the end with no room for its terminator is
refused for that, and the tests assert which reason was given because the second check would otherwise hide
the first being removed.

**Why the sabotage entries are built replies and not the device.** No entry could be produced here. The
alternatives were an elevated run, which is not available, or leaving the entry decoding untested; the
decoding is the larger part of the module, so it is tested against replies laid out with `offset_of!` from the
structure's declared types, with every field given a value no other field has, and against a fake device that
asserts the buffer is aligned, never smaller than 72 bytes, and has the version set on every attempt including
the regrow.

**What was weighed.** `with_capacity` raises a size below 72 to 72 and does not refuse it: the call's own
refusal of a smaller buffer is a fact about the call, and the request has no use for a first size it could not
make.

## D-25: the SDDL conversion

**Text only, hiding the binary form.** The engineer asked whether the descriptor could be exposed only
as SDDL, so that no conversion function is public. The binary form is already public in a published
crate (`capture`, `as_ptr`, `as_bytes`), so hiding it would be a breaking removal that still converts
inside, and a request has to hold the binary form anyway: `CreateFileW` takes a pointer, and a
request is built on one thread and performed on another ([D-8](DESIGN-NOTES.md#d-8)). Storing the
text and converting in `perform` would have moved the failure to the worker and repeated the
conversion, and the string would still have been validated at construction. The restriction was
pointed at durable-ioring instead, which decides which of this crate's parameters an open may use
([DI-D-63](../durable-ioring/DESIGN-NOTES.md#di-d-63)), and the engineer's ruling was that the
conversion functions are public here.

**Trusting the reported length.** The first version of `to_sddl` read the text through the length
Windows reports, which is documented to include the terminator. A probe on strings of several shapes
found the figure is not reliable, and the first symptom was a NUL on the end of one rendering and
a refusal for several others that Windows had not made: the check this crate made on the length was
what failed. Reading up to the terminator is the standard contract for a string Windows returns, and
needs no length.

**A mask newtype, not a bare integer.** The mask is over `OBJECT_SECURITY_INFORMATION`, which Windows
extends (labels, attributes, scopes, the protected and unprotected DACL bits), so the five parts the
documentation names are constants and the rest travel through `from_bits`, as the flag types of
[D-12](DESIGN-NOTES.md#d-12) do.

## D-26: `ReOpenFile`

**A borrowed handle.** The request could have borrowed the original for the lifetime of the request,
as a `BorrowedHandle`. That ties every request to the scope that built it, and a request here is
built on one thread and performed on another ([D-8](DESIGN-NOTES.md#d-8)); an owned duplicate costs
one `DuplicateHandle` and removes the lifetime, which is what the volume hint of
`OpenFileByIdentifier` already does.

**Defaults.** The calls that reopen are usually made to change one thing about how a file is open,
which suggests defaulting the others to the original's. `ReOpenFile` has no such mode: the three
values are the new open's, and a share mode of zero is an exclusive one. A default chosen here would
be a policy about which opens to allow, which this crate leaves to its consumers
([DI-D-63](../durable-ioring/DESIGN-NOTES.md#di-d-63)).

**Directories.** Every directory attempt was refused with access denied, across access masks, flags
and share modes, and across the directory handles tried. The measurement is recorded and a test
asserts it, so that a change in what Windows answers is seen rather than assumed.

## D-27: the path entries are built fallibly

**Where an interior NUL is reported.** `NR-3.5` left two places open: the constructors, or `perform`.
`perform` returns `Outcome<T>`, whose error is the raw Win32 code and nothing else, so reporting the
NUL there needs either a new error on every entry's `Request` impl or a code Windows did not
produce, and the crate does not invent codes (see `FullPathError::Unstable`, which exists for that
reason). The constructors were already going to be fallible for `OpenFile` and `WatchDirectory`,
since reading the current directory can fail and the graft is computed there, and a caller who can
still act on a bad path is on the submitting thread, which is where a handle that cannot be captured
is failed too ([D-7](DESIGN-NOTES.md#d-7)). So the choice was one error type at construction against
two places for one kind of failure.

**`ResolveFullPath::new` fallible for one variant.** It reads no directory, so only `InteriorNul` can
come out of it, and a `Result` whose error type has variants it never produces is a small
untidiness. The alternative was adding an `InteriorNul` variant to `FullPathError`, which is already
`#[non_exhaustive]` and already the error of its `perform`, so it would not break a caller. That
leaves a NUL to be found on whatever thread performs the call, a worker that submitted nothing, and
it makes this one entry the odd one out in where the three report the same thing. The shared type
was preferred, and the variant that can only come from the other two is documented on the
constructor.

**An empty path is not refused.** `prepare` refused it with its own failure. A request that does
refuse it adds a check this crate's other entries do not have, and `CreateFileW` already answers an
empty path with a code. `win-durable-ioring`'s open keeps refusing it, as invalid input, because it
did before and a caller of that crate must not see a difference.