Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
012e307
docs(namespace-request): GetFullPathNameW is not lexical, and say why…
Sep 10, 2026
80acadb
docs(probes): finish the GetFullPathNameW sweep and point at the deci…
Sep 10, 2026
04ef096
docs: finish the GetFullPathNameW sweep across the workspace
Sep 10, 2026
bec2dad
docs(namespace-request): quote the cost figure for what it measures
Sep 10, 2026
4e3a277
docs: separate what GetFullPathNameW does lexically from what it roots
Sep 10, 2026
f2807a7
docs(namespace-request): say what the probe actually establishes abou…
Sep 10, 2026
6160ef9
docs(namespace-request): make the cost a bound, and fix three path cl…
Sep 10, 2026
c61ea2d
docs: qualify the rooting clause everywhere, and put each fact in its…
Sep 10, 2026
b778ea2
docs(namespace-request): drop the unmeasured cost claim, narrow the c…
Sep 10, 2026
b22a0df
docs: finish the contract sweep into the probe crate, and stop a test…
Sep 10, 2026
745efb1
test(namespace-request): cover the drive-relative rooting form
Sep 10, 2026
fd32f07
test(namespace-request): make the drive-relative test independent of …
Sep 10, 2026
990f2e5
test(namespace-request): fix the case dependence, the vacuous assert,…
Sep 10, 2026
df75187
docs: GetFullPathNameW does touch the filesystem, and writes the envi…
Sep 10, 2026
edc9cfb
test(namespace-request): pin the per-drive entry arm, and stop unders…
Sep 10, 2026
7bffb51
docs: the drive-entry gate is shape AND existence, not existence inst…
Sep 10, 2026
7411afd
test(namespace-request): stop deriving the drive-entry probes from th…
Sep 10, 2026
640df05
test(namespace-request): choose the probe drive letter at runtime
Sep 10, 2026
79b3a6d
test(namespace-request): enforce the probe directory's form instead o…
Sep 10, 2026
092bda8
fix(namespace-request): grow the entry buffer, and stop the probe con…
Sep 10, 2026
fef59ae
fix(probes): the gap spans one NET allocation, not two
Sep 10, 2026
83954ca
docs(probes): let the design-session link render
Sep 10, 2026
13882df
test(namespace-request): pick the probe drive against the probe direc…
Sep 10, 2026
df5ffd2
test(namespace-request): derive the nonexistent path, and correct fou…
Sep 10, 2026
5903b4c
test(namespace-request): validate every probe-drive candidate, not ju…
Sep 10, 2026
8f921da
test(namespace-request): restore borrowed drive entries on unwind, an…
Sep 10, 2026
016f032
fix(namespace-request): tell an empty per-drive entry from an absent one
Sep 10, 2026
fe3b6b4
docs(probes): stop the report contradicting its own decomposition
Sep 10, 2026
e13aac7
fix(namespace-request): state the current-drive arm at the boundary o…
Sep 10, 2026
9a6e156
docs: sweep the current-drive arm into every restatement of the rooti…
Sep 10, 2026
137b544
fix(namespace-request): close five rounds of vacuous tests and unswep…
Sep 10, 2026
4198862
fix(namespace-request): NUL is the one device word a path in front of…
Sep 10, 2026
c20ef91
fix(namespace-request): the lexical half claimed a mechanism too, for…
Sep 10, 2026
3e48630
fix(namespace-request): take the handle-allocation guard, and say whi…
Sep 10, 2026
65d1706
refactor(namespace-request): split full_path/tests.rs, extract the dr…
Sep 10, 2026
dbbecce
fix(namespace-request): assert which device each name reaches, and re…
Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,23 @@ jobs:
RUST_BACKTRACE: 1
RUST_LIB_BACKTRACE: 1
run: cargo test --workspace --locked --no-fail-fast
# The UNC current-directory test is ignored by default because it needs a
# reachable administrative share (`\\localhost\C$`), which an ordinary
# developer account does not have -- running it by default would report an
# environment limitation as a defect. The runner is an administrator, so
# the coverage is deterministic HERE, which is where it has to be: this is
# the only test that puts `GetFullPathNameW` under a UNC current directory,
# and the guarantee it pins ("a root-relative path takes the ROOT of the
# current directory, which is a share root and not a drive") is public.
#
# A FAILURE HERE IS A RESULT. If the share stops being reachable on the
# runner the test says so in those terms, and the answer is to provision a
# UNC fixture rather than to delete the assertion.
- name: cargo test (namespace-request, UNC current directory)
env:
RUST_BACKTRACE: 1
RUST_LIB_BACKTRACE: 1
run: cargo test -p windows-namespace-request-sys --locked --test unc_current_directory -- --include-ignored
# windows-file-watcher-example-test-harness is a normal workspace member
# that depends on windows-file-watcher with the test-util feature, so
# the two `--workspace` steps above unify that feature onto
Expand Down
12 changes: 8 additions & 4 deletions CHECKLIST-thread-ambient.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ pool, or async anywhere near it. The family grows by one entry per Win32 call.
| 6 | `GetFileInformationByHandle` (non-Ex) | watcher | `BY_HANDLE_FILE_INFORMATION`; a distinct call, not a class of entry 5 |
| 7 | `GetFinalPathNameByHandleW` | watcher directly, Globazog via `std::fs::canonicalize` | `VOLUME_NAME_DOS \| FILE_NAME_NORMALIZED` |
| 8 | `GetVolumeInformationByHandleW` | watcher | handle-based, not the path-based `GetVolumeInformationW` |
| 9 | `GetFullPathNameW` | enumeration | lexical only |
| 9 | `GetFullPathNameW` | enumeration | result not verified |

Four audit findings that shape the milestones below, recorded because each contradicts an assumption the
first draft of this plan was written on.
Expand Down Expand Up @@ -417,9 +417,13 @@ Entries 5-9 of the audited list. All but the last take a handle, so all but the
filesystem name. Handle-based; the path-based `GetVolumeInformationW` is deliberately not in round one
because no audited consumer calls it.

- [x] **M26.5** -- The `GetFullPathNameW` entry. Lexical only: it resolves relative components and `.`/`..`
and never expands a drive letter, so it does **not** close the session-relative hazard from M20.1, and
its documentation must say which problem it solves and which it leaves standing.
- [x] **M26.5** -- The `GetFullPathNameW` entry. Does not verify its result: it collapses `.`/`..`
lexically and roots most paths that are not fully qualified against process state -- the current
directory, or for a drive-relative path naming another drive the entry recorded for that drive,
while on the current drive that entry makes no difference -- and never expands a
drive letter, so it
does **not** close the session-relative hazard from M20.1, and its documentation must say which
problem it solves and which it leaves standing.

- [x] **M26.6** -- Acceptance, in **two** parts, because the audit had two purposes and checking only the
first is how the coverage question got missed once already.
Expand Down
25 changes: 23 additions & 2 deletions CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ they are deliberately separate from building the new facility, which cannot star
- [ ] **M20.1** -- Decide what the namespace facility does with a session-relative drive letter, and record
it as a decision rather than leaving the absence of one implicit. Path resolution follows the
impersonated token's logon session (measured: under a token from another logon session with unchanged
local access, the global `C:` resolved and a `subst` letter did not), and `GetFullPathNameW` is lexical
so submission-time canonicalisation does not expand the letter. `QueryDosDeviceW` distinguishes a real
local access, the global `C:` resolved and a `subst` letter did not), and `GetFullPathNameW` never
expands a drive letter, so submission-time canonicalisation does not expand it either. `QueryDosDeviceW` distinguishes a real
local volume, a `subst`, and a network mapping cheaply, so detection is settled and only the response is
open: expand to a session-independent form at submission, or reject at admission with a typed error.
Expansion is not uniform -- a network mapping becomes a UNC path, a local volume becomes a device path
Expand Down Expand Up @@ -195,3 +195,24 @@ Ungated work with no identified predecessor deliverable.
probe because a `LOGON32_LOGON_NEW_CREDENTIALS` token answered the question with a passing control, so
the fallback was redundant -- not because the crash was understood. Parked rather than dropped so the
unexplained result is not mistaken for a tested one.

- [ ] **M-inf.2** -- Archive the eight completed milestone groups in
[CHECKLIST-thread-ambient.md](CHECKLIST-thread-ambient.md) into
[COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md).

**Raised by a review that named one item, and measured to be eight groups.** The comment asked for
M26.5's completed multi-line body to be replaced by a one-line stub, per the checklist-hygiene rule
that an active checklist is an action queue. That rule is right and the file does violate it -- but
M26.5 is not exceptional: its five siblings in M26 are written the same way, so stubbing only the
reported item would have made it inconsistent with the group it belongs to rather than more
consistent with the rule.

Counted rather than assumed, every group in the file is complete and due for migration under the
"move the completed group" rule: M22 (8 items), M23 (6), M24 (6), M25 (7), M26 (6), M27 (6),
M28 (4) and M29 (5). Only `M26+` has open items, and it is what keeps the file alive.

Not taken in PR #86 because that branch corrects `GetFullPathNameW` documentation and touched
M26.5 only to fix one technical premise inside it. Migrating roughly 400 lines of another feature's
bookkeeping through it would bury the change it exists to make. The migration is mechanical, is its
own commit, and needs the group headings dated per the archive format -- date-only on the `## Moved`
line, with any precise timestamp reserved for an anchored item heading.
15 changes: 12 additions & 3 deletions DESIGN-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1459,9 +1459,18 @@ Two corollaries that decide the design:
- **Submission-time canonicalisation does not close this.** The path must be
resolved on the calling thread at submission, because the process current
directory is mutable by any thread and even perfect remoting would be racy --
but `GetFullPathNameW` is *lexical*. It resolves relative components and
`.`/`..` and never expands a drive letter, so the "canonical" path still
carries a session-relative reference.
but `GetFullPathNameW` never expands a drive letter, so the "canonical" path
still carries a session-relative reference. (It is not *lexical* as a whole
either, which matters elsewhere but not here: it collapses `.`/`..` lexically
but roots most paths that are not fully qualified against process state -- the
current directory, or for a drive-relative path naming a drive OTHER than the
current one the entry recorded for that drive, which moves independently of
it; on the current drive that entry makes no difference and the process
current directory wins -- and that rooting is the property
submission-time resolution buys. See
`windows-namespace-request-sys`'
[DESIGN-NOTES.md](crates/windows-namespace-request-sys/DESIGN-NOTES.md) ->
`D-18`.)
- **The extended-length prefix does not help.** `\\?\Z:\dir` still resolves `Z:`
through the device map; the prefix skips Win32 normalisation, not
object-manager resolution. Only UNC, `\\?\Volume{GUID}\`, and
Expand Down
11 changes: 11 additions & 0 deletions crates/windows-namespace-request-sys/CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Checklist: windows-namespace-request-sys

Design decisions are in [DESIGN-NOTES.md](DESIGN-NOTES.md), and how they were
reached in [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md). This crate's *creation* is
tracked separately, in the workspace
[CHECKLIST-thread-ambient.md](../../CHECKLIST-thread-ambient.md) milestones
M24-M26; that file is feature-scoped and is deleted when its feature completes,
so durable follow-up work for the crate belongs here instead.

No open milestones. Completed work is in
[COMPLETED-CHECKLIST.md](COMPLETED-CHECKLIST.md).
59 changes: 59 additions & 0 deletions crates/windows-namespace-request-sys/COMPLETED-CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Completed checklists: windows-namespace-request-sys

Append-only. Newest groups at the bottom.

## Moved 2026-09-10 -- NR-1: the per-drive current-directory arm, pinned

### <a id="nr-11"></a>NR-1.1 -- Pin the `=X:` arm of drive-relative rooting. *(completed 2026-09-10 12:24:46 UTC-04:00)*

- [x] **NR-1.1** -- Decide how this crate's tests may control process-global
state, then pin the `=X:` arm of drive-relative rooting.

**Completed in the round that raised it, because the blocker turned out not to
exist.** The item was written to defer the work: pinning the arm needs a
controlled `=X:`, and the two routes to one -- mutating process-global state
that other test threads share, or spawning a child process with a crafted
environment -- looked like a decision about this crate's test shape rather than
something to take in passing.

Measurement dissolved the first route's objection within the hour.
`GetFullPathNameW` **itself** writes the `=X:` entry -- when resolving for a
drive other than the current one, and when the recorded entry is absent or
rejected, in which case it is written as the drive root. (An accepted entry is
left alone, and the current-drive form writes nothing.) The code under test
therefore already mutates that state on the very path these tests exercise, so
a test that sets it first introduces no hazard that resolving alone did not,
and there was nothing left to decide. Isolation across the tests comes from
their disjoint drive letters.

`a_drive_relative_path_uses_that_drives_entry_verbatim_and_rewrites_a_bad_one`
in [drive_entry.rs](src/full_path/tests/drive_entry.rs) now pins all three behaviours: an entry
naming an existing directory is honoured verbatim (onto a *different* drive,
which is what makes "that drive's own current directory" a convention rather
than a guarantee); an entry naming nothing is rejected in favour of the drive
root; and the call writes the entry back, creating it on a host that had none.
It draws its drive letter from a list disjoint from every sibling test's, so
no two can select the same one and race under libtest's thread-per-test model.

*(Later correction, recorded here because the archive is history and the
history was briefly wrong: this entry described the selection as a `W`/`U`
**pair**, matching the helper as written. That helper validated only its first
letter and returned the second unchecked, so the guarantee the pair implied did
not hold. The lists are now three letters each, every candidate is checked
against both the current drive and the probe drive, and the lists themselves
live in one table in [drive_entry.rs](src/full_path/tests/drive_entry.rs) with a test enforcing
that they stay disjoint and long enough. This note deliberately does NOT
enumerate them: an earlier version did, and named five lists after a sixth had
been added -- so a reader picking letters for a seventh would have consulted
an inventory missing three of the letters already in use. The table is the
inventory.)*

The sibling test keeps its weaker form and now says so: without controlling
the entry it can only bound the arm.

*(Later correction: that sibling was named
`a_drive_relative_path_is_rooted_at_that_drive_and_not_the_process_directory`,
and the name claimed two things its assertions do not reach -- `ends_with`
accepts any base, including the process directory, and an accepted entry is
used verbatim so the result need not be on that drive at all. It is now
`a_drive_relative_path_carries_its_component_and_the_current_drive_uses_the_process_directory`.)*
5 changes: 5 additions & 0 deletions crates/windows-namespace-request-sys/COMPLETED-PLANS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Completed plans: windows-namespace-request-sys

| Path to CHECKLIST.md | Completion Date | Brief description | Design Notes |
|---|---|---|---|
| [CHECKLIST.md](CHECKLIST.md) | 2026-09-10 | NR-1: pinned the `=X:` arm of drive-relative rooting. Raised as deferred work and completed the same round, because the blocker dissolved on measurement -- `GetFullPathNameW` writes the per-drive entry itself, so a test that sets it adds no hazard. The test now pins verbatim honouring, drive-root fallback, and the write-back. | [DESIGN-NOTES.md](DESIGN-NOTES.md#d-18), [DESIGN-RATIONALE.md](DESIGN-RATIONALE.md) |
15 changes: 15 additions & 0 deletions crates/windows-namespace-request-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ wtf-string = { version = "0.1.0", path = "../wtf-string" }
[dev-dependencies]
windows-thread-ambient-sys = { version = "0.2.0", path = "../windows-thread-ambient-sys" }

# Test-only, for the hidden `=X:` per-drive current-directory entries. Rust's
# `std::env` cannot reach them at all -- it rejects a key containing `=` -- and
# they are the state `GetFullPathNameW` reads and rewrites when it roots a
# drive-relative path, so a test that does not control them can only bound that
# behaviour rather than pin it. A dev-dependency, so no consumer inherits the
# feature.
[dev-dependencies.windows-sys]
Comment on lines +42 to +48
version = "0.61.2"
default-features = false
# `Win32_Foundation` is named here rather than inherited from the dependency
# below: reading an entry has to clear and re-read the last error to tell an
# empty value from an absent one, and a test target must not depend on a
# feature staying enabled for a reason unrelated to it.
features = ["Win32_Foundation", "Win32_System_Environment"]

[dependencies.windows-sys]
version = "0.61.2"
default-features = false
Expand Down
Loading