Say precisely what GetFullPathNameW does, and why it stays - #86
Conversation
… 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>
The two commits before this corrected the crate that owns the call and the probe that consumes it. Sweeping `lexical` across the whole workspace found four more statements of the same wrong fact, three of which neither commit touched -- including one in a file the first commit had already edited. - `full_path/tests.rs` still said "the call is lexical" in a second comment forty lines below the one that was fixed. The reported site is a sample, not the population, and that holds even when the population is one file. - The root DESIGN-NOTES stated it as a premise of a design corollary. The corollary's conclusion is right and unchanged -- a drive letter survives submission-time canonicalisation -- but it was resting on a false reason, so it now gives the true one and links D-18 for the rest. - Root CHECKLIST.md M20 used it as a live premise inside an OPEN milestone, which is the most expensive of the four: a reader executing that work would have inherited the wrong reason for a correct conclusion. - CHECKLIST-thread-ambient.md stated it twice, in the entry catalogue table and in M26.5. Both are completed records, and the correction is confined to the technical premise; the request each recorded is untouched, and M26.5's is in fact what D-18 now satisfies. Deliberately not touched: the 2026-08-27 design session uses "lexical canonicalisation" generically and correctly, and a Tier 3 transcript is a raw record rather than a normative statement. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟢 Approval recommended
The changes are documentation/design-note corrections with a consistent workspace-wide sweep and no remaining inconsistencies found in the updated hunks.
Pull request overview
This PR corrects and precisely documents GetFullPathNameW semantics (not “lexical”, and “touches no filesystem” vs. “reads mutable process state”), and records the decision to keep it because submission-time resolution is the property the crate needs.
Changes:
- Updates
windows-namespace-request-sysdocs (and its design notes) to state exactly whatGetFullPathNameWdoes, why it remains the right call, and names the genuinely-lexical alternatives. - Sweeps and corrects restatements across the workspace (design notes, checklists, probe docs, tests/examples) to remove the incorrect “lexical” claim.
- Marks the
windows-platform-probeschecklist item M2.6 complete and links readers to the owning-crate decision (D-18) instead of re-deriving assumptions in the probe.
File summaries
| File | Description |
|---|---|
| DESIGN-NOTES.md | Updates the root design note to remove the incorrect “lexical” rationale and point to windows-namespace-request-sys decision D-18. |
| crates/windows-platform-probes/src/request_cost.rs | Adds a pointer to the owning crate’s D-18 decision to avoid probe-side re-derivation. |
| crates/windows-platform-probes/src/bin/device_map.rs | Rewords output text to remove the incorrect “lexical resolution” phrasing. |
| crates/windows-platform-probes/CHECKLIST.md | Marks M2.6 as completed. |
| crates/windows-namespace-request-sys/tests/acceptance/operations.rs | Updates an acceptance comment to remove “lexical” wording. |
| crates/windows-namespace-request-sys/src/path.rs | Corrects the crate docs to state “touches no filesystem” and “not lexical”, including the process-state inputs. |
| crates/windows-namespace-request-sys/src/full_path/tests.rs | Updates test docs/comments to remove the “lexical” claim and keep the “touches no filesystem” property. |
| crates/windows-namespace-request-sys/src/full_path.rs | Expands and clarifies the module docs: what it solves, why not the lexical alternatives, and what is/ isn’t established about kernel transitions. |
| crates/windows-namespace-request-sys/DESIGN-NOTES.md | Adds D-18 capturing the correction, the “keep GetFullPathNameW” decision, and the named alternative(s). |
| CHECKLIST.md | Updates M20.1 premise to remove “lexical” and retain the true invariant (“never expands a drive letter”). |
| CHECKLIST-thread-ambient.md | Updates the audited call table and M26.5 record to remove “lexical” and preserve the correct properties. |
Review details
- Files reviewed: 11/11 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
Several statements exceed the documented Win32 contract, make unmeasured cost claims, and leave completed checklist records unarchived.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (4)
crates/windows-namespace-request-sys/src/full_path/tests.rs:50
- This test only establishes that the resulting path need not exist; it cannot establish that the API never accesses the filesystem. Phrase the comment in terms of the behavior under test.
// The call touches no filesystem. A consumer wanting a verified path wants
// an open plus GetFinalPathNameByHandleW.
crates/windows-namespace-request-sys/src/full_path.rs:147
- The example should not claim that no filesystem access occurs when the documented contract only guarantees no validity/existence check.
/// // `.` and `..` are resolved without touching the filesystem.
crates/windows-namespace-request-sys/src/full_path.rs:164
- Resolving a nonexistent path proves that existence is not checked, but not that the function performs no filesystem access. Use the actual documented guarantee here.
/// // A path to nothing resolves perfectly happily, because the call
/// // touches no filesystem. A consumer wanting a verified path wants an open plus
/// // GetFinalPathNameByHandleW instead.
CHECKLIST-thread-ambient.md:423
- This completed record now embeds the same unsupported zero-filesystem-access claim. The documented behavior is that the result is not checked for validity or existence.
- [x] **M26.5** -- The `GetFullPathNameW` entry. Touches no filesystem: it resolves relative components
and `.`/`..` against the process current directory, 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.
- Files reviewed: 11/11 changed files
- Comments generated: 11
- Review effort level: Balanced
Self-review caught this, and it is the same defect D-18 exists to correct, committed inside the paragraph warning against it. The doc said `probe-request-cost` "measures roughly 212 ns per resolution". It does not. The probe reports `prepare_short_path` as an explicit construct-and-drop cycle -- an allocation, this resolution and a drop together -- and says in its own output that it declines to say what the total is made of. Attributing it to the resolution names a mechanism the measurement does not isolate, which is exactly what "a syscall cost" and "lexical" each did before it. Verified by running the probe rather than by reading the number: it prints 208.0 ns for `prepare_short_path` on this x86_64 host under the heading "the first four are construct-and-drop cycles". The figure now bounds the call from above and claims nothing else. Swept both restatements. The probes CHECKLIST said "~212 ns for a build ... and declines to say what that is made of", which was accurate already; it was the restatement in this crate that drifted, in both the module doc and D-18. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Unsupported cost claims and repository documentation/checklist conventions remain unresolved.
Review details
Suppressed comments (5)
crates/windows-namespace-request-sys/src/full_path.rs:35
- The claim that either alternative is cheaper is not established here: the cited probe measures
GetFullPathNameWonly, Microsoft documents no comparative cost, andPathAllocCanonicalizeperforms its own allocation. Since the decision depends only on avoiding process-state reads, remove the performance claim unless a direct measurement is added.
//! One exists: `PathCchCanonicalizeEx`, or `PathAllocCanonicalize`. Either is
//! cheaper and reads no process state at all.
//!
//! **They are the wrong call here, and the reason is the property above rather
crates/windows-namespace-request-sys/DESIGN-NOTES.md:558
- This decision repeats an unverified performance conclusion. The available probe does not benchmark either canonicalizer, and the platform documentation specifies behavior rather than relative cost. State the relevant property—no process-state lookup—without calling the alternatives cheaper.
**The decision: keep `GetFullPathNameW`.** A genuinely lexical canonicalizer
exists -- `PathCchCanonicalizeEx`, or `PathAllocCanonicalize` -- and is cheaper,
reading no process state at all. It is the wrong call here, and for the property
rather than the price: resolving against the current directory *at submission* is
what this crate is buying. A lexical canonicalizer would leave a relative path
crates/windows-platform-probes/src/request_cost.rs:83
- Calling the alternative “cheaper” reintroduces an unsupported mechanism/cost claim into the probe documentation. Neither alternative is measured by this probe, so describe it only as lexical or process-state-independent.
//! `D-18`, which states what the call actually does, records keeping it over
//! the cheaper lexical alternative, and says plainly that whether it enters the
//! kernel is not established.
crates/windows-platform-probes/CHECKLIST.md:91
- Checking this multi-line item leaves a large completed item in the active checklist. The repository’s completed-item rule requires moving an item over 100 characters (or with sub-items) immediately to
COMPLETED-CHECKLIST.mdand replacing it here with the one-line anchored completion stub.
- [x] **M2.6** -- Say precisely what `GetFullPathNameW` does, in the crate that owns it, and decide
crates/windows-namespace-request-sys/DESIGN-NOTES.md:537
- D-18 adds a new decision but keeps its correction history, sweep narrative, and rejected first-draft reasoning entirely in Tier 1. The repository’s design-document rules require new decisions in a component with substantial design history to record rationale in
DESIGN-RATIONALE.mdin the same commit; keep the current decision and constraint here, and move/link the historical material to Tier 2.
## <a id="d-18"></a>D-18: `GetFullPathNameW` is not lexical, and the genuinely lexical alternative is the wrong call
- Files reviewed: 11/11 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Independent review of the previous commits found the correction had overshot: saying the call resolves `.`/`..` "against the process current directory" attributes process-state dependence to the one operation that has none. That is the same imprecision D-18 was written to fix, running the other way. Measured rather than reasoned. With the process CWD set to C:\Windows and then C:\Users, a fully-qualified `C:\a\..\b` resolves to `C:\b` both times, while `rel.txt` follows the CWD. So the call does two separable things: it collapses `.`/`..` and normalizes separators, which IS lexical and reads no process state; and it roots a path that is not fully qualified, which reads the current directory or the drive's own `=C:` entry. It is not lexical AS A WHOLE, and only the rooting half is the property submission-time resolution buys -- which is what D-18's decision actually turns on. Corrected in all four places that had merged the two: the module doc, path.rs, the root DESIGN-NOTES and M26.5. Two further review findings: - D-18 undercounted its own sweep as "six places" and "four further restatements". The real figure is nine sites across five files, three of them in full_path.rs alone. A paragraph whose point is "the reported site was a sample, not the population" should not itself undercount the population. - M2.6 was checked off while its body still read as pending -- quoting the module doc text this change deletes, and saying the correction "is queued rather than taken". Rather than restate it in the past tense, the item is archived per the completed-item rule (2411 characters, so large) with a one-line stub pointing at it, and the archive records the resolution above the request it preserves. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The documentation asserts an unmeasured performance ordering and has unresolved design-record formatting and rationale issues.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (3)
crates/windows-namespace-request-sys/src/full_path.rs:42
- Calling both alternatives “cheaper” is an unsupported performance claim: the only measurement documented below is the current construct-and-drop path, and
PathAllocCanonicalizeperforms its own allocation. The semantic argument does not need this claim; describe only the observable contract unless an isolated comparison is added.
//! One exists: `PathCchCanonicalizeEx`, or `PathAllocCanonicalize`. Either is
//! cheaper and reads no process state at all.
crates/windows-namespace-request-sys/DESIGN-NOTES.md:566
- This repeats the unmeasured “cheaper” comparison as part of the canonical decision. The probe explicitly does not isolate this call, much less compare it with either PathCch API, so retain the relative-path semantics that decide the choice and remove the cost assertion.
**The decision: keep `GetFullPathNameW`.** A genuinely lexical canonicalizer
exists -- `PathCchCanonicalizeEx`, or `PathAllocCanonicalize` -- and is cheaper,
reading no process state at all. It is the wrong call here, and for the property
crates/windows-platform-probes/src/request_cost.rs:83
- This cross-reference turns the unmeasured “cheaper” comparison into probe documentation. D-18’s decisive fact is that the alternative leaves relative input relative; refer to that contract rather than asserting a performance ordering the probe never measured.
//! `D-18`, which states what the call actually does, records keeping it over
//! the cheaper lexical alternative, and says plainly that whether it enters the
//! kernel is not established.
- Files reviewed: 12/12 changed files
- Comments generated: 4
- Review effort level: Balanced
…t cost Second review round. The previous commit over-corrected the first one, in the opposite direction, and both errors were about the same number. The first draft called ~210 ns a per-resolution cost, attributing to this call a total that also contains an allocation and a drop. Correcting that, the second draft said the probe "declines to decompose" the total. It does decompose: the report states that recycling an already-resolved path pays ~42 ns against ~210 ns, and that the ~168 ns difference is the resolution rather than the allocation -- which is exactly why recycling is the only one of its two candidate optimizations that can touch it. What the probe withholds is the MECHANISM, not the division. So the doc now quotes ~168 ns as this call's measured share, and says the number is silent on whether any of it entered the kernel. Also corrects which label the ~210 ns belongs to. It is the whole request build; `prepare_short_path` is a different, lower figure. Measured over three runs on this x86_64 host: prepare 200-206 ns, build 208-218 ns, clone 43-69 ns. An earlier single run had read 287 ns for the build, which was noise -- these probes are not deterministic, which is why the repo's own control for them exists. Two further fixes: - The two-part split omitted a third rooting form. `\foo` takes only the current DRIVE from process state, yielding `C:\foo` -- not the current directory's subtree, which the previous wording implied. Measured under two different current directories. The lexical half now also names trailing dot/space trimming and legacy device mapping (`CON` -> `\\.\CON`), which a path-preparing crate's callers can hit. - D-18's sweep narrative counted the doc examples twice. The nine/three/two totals were right; the breakdown now adds to nine. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The documentation makes unsupported performance attributions and does not follow the repository’s design and checklist archival conventions.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (7)
crates/windows-namespace-request-sys/src/full_path.rs:48
- The “cheaper” comparison is not established: the probe never invokes either PathCch alternative, and the API documentation makes no relative-performance guarantee. Since this change is specifically avoiding mechanism claims beyond the evidence, please remove the cost claim (or add an actual benchmark) and keep the decision based on the required rooting behavior.
//! One exists: `PathCchCanonicalizeEx`, or `PathAllocCanonicalize`. Either is
//! cheaper and reads no process state at all.
crates/windows-namespace-request-sys/DESIGN-NOTES.md:566
- The new decision records “cheaper” as a fact, but no benchmark in this repository calls either PathCch alternative, and their API contracts provide no comparative performance guarantee. Remove the unsupported cost conclusion or benchmark the alternatives; the current-directory-rooting requirement is sufficient to justify the decision.
**The decision: keep `GetFullPathNameW`.** A genuinely lexical canonicalizer
exists -- `PathCchCanonicalizeEx`, or `PathAllocCanonicalize` -- and is cheaper,
reading no process state at all. It is the wrong call here, and for the property
crates/windows-platform-probes/src/request_cost.rs:83
- This repeats the unmeasured assertion that the lexical alternative is cheaper. The probe does not call
PathCchCanonicalizeExorPathAllocCanonicalize; it only compares building a request with cloning an existing path. Describe the alternative by its established behavior, not by an unsupported relative cost.
//! The owning crate now settles both halves rather than leaving them to be
//! re-derived from a probe: see `windows-namespace-request-sys`'
//! [DESIGN-NOTES.md](../../windows-namespace-request-sys/DESIGN-NOTES.md) ->
//! `D-18`, which states what the call actually does, records keeping it over
//! the cheaper lexical alternative, and says plainly that whether it enters the
//! kernel is not established.
crates/windows-platform-probes/COMPLETED-CHECKLIST.md:104
- The resolution summary turns the unmeasured “cheaper” premise into a completed finding. No probe here invokes
PathCchCanonicalizeEx, so the archive should record only the established semantic difference unless a comparative benchmark is added.
`GetFullPathNameW` collapses `.`/`..` lexically but roots a path that is not fully qualified against
process state, so it is not a lexical call as a whole; `PathCchCanonicalizeEx` is genuinely lexical,
cheaper, and the wrong call, because rooting at submission is the property being bought. Whether it
enters the kernel is recorded as NOT established. The item's body below is the request as it was
crates/windows-platform-probes/COMPLETED-CHECKLIST.md:97
- This archive entry does not follow the repository’s completed-item format (
.github/copilot-instructions.md:1398-1412): the date group must be a##heading, followed by a separate###anchored item heading that repeats the stub summary and carries the inline completion timestamp. Keeping that shape makes the stub target and archive index consistent with the other completed items.
## <a id="m26"></a>Moved 2026-09-09 22:54:01 -04:00 -- M2.6: what `GetFullPathNameW` does, and whether it stays
crates/windows-namespace-request-sys/DESIGN-NOTES.md:537
- This new D-18 section mixes the current decision with draft history and alternative-analysis prose, but the repository’s design-document convention requires new decisions to be written to both Tier 1
DESIGN-NOTES.mdand Tier 2DESIGN-RATIONALE.mdin the same commit (.github/copilot-instructions.md:1495-1512). Please keep a compact canonical decision here and add the historical reasoning to a cross-referenced Tier 2 file.
## <a id="d-18"></a>D-18: `GetFullPathNameW` is not lexical, and the genuinely lexical alternative is the wrong call
crates/windows-namespace-request-sys/DESIGN-NOTES.md:560
- Repository documentation requires file references to be clickable links.
path.rsis the only explicit file name in this inventory left as inline code; link it like the adjacentfull_path.rsandtests.rsreferences.
with two more in `path.rs`, two in [tests.rs](src/full_path/tests.rs), one in an
- Files reviewed: 12/12 changed files
- Comments generated: 3
- Review effort level: Balanced
…aims Third review round. The pattern is now explicit in D-18: each of the three previous drafts of the cost paragraph named a mechanism the evidence did not reach, in a different way. COST. The last draft subtracted `clone_prepared_units` from `build_open_request` and called the ~168 ns remainder "this call's measured share". The subtraction does not isolate the call. `build_open_request` runs the whole preparation step plus the OpenFile builder chain, and `prepare` makes TWO heap allocations of its own -- a copy of the input and a MAX_PATH output buffer -- against the clone's one. So the gap is dominated by allocator work the same sentence was busy excluding. Measured, five runs on this x86_64 host: prepare 211.5, build 218.4, clone 47.0, so the builder chain is ~7 ns and the gap ~165 ns (the 168 was subtracting two separately rounded figures). Timed directly with the input marshalled and the output buffer pre-allocated, so no allocation is in the loop, the call itself is ~110 ns over 200k iterations -- about two thirds of the gap. The doc now states the probe's number as an upper bound, says plainly that no instrument here isolates the call, and labels the direct measurement as not being a probe output. PATHS. Three further corrections, each measured: - A legacy device name SHORT-CIRCUITS the rooting rather than adding to the rewriting. `CON` -> `\\.\CON` and is not rooted at all, so it was a counterexample to the unconditional "roots a path that is not fully qualified" and to the claim that unqualified inputs vary with process state -- `CON` is unqualified and invariant. It is exact-match only: `CON.txt` and `a\CON` root normally, and `\CON` -> `Q:\CON`. - `\foo` takes the ROOT of the current directory, not its drive. With the current directory set to \\localhost\C$\Windows it yields \\localhost\C$\foo, which has no drive letter at all. This crate handles \\?\UNC\ paths, so the UNC case is in scope. - The root DESIGN-NOTES and M26.5 still said rooting was "against the process current directory", which the drive-relative case disproves: setting =C: while leaving the current directory alone changes what `C:foo` resolves to. Both now match D-18 and path.rs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The documentation includes version-dependent device-name behavior and unsupported performance claims, alongside required design/checklist formatting issues.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (8)
crates/windows-namespace-request-sys/src/full_path.rs:56
- Neither the Windows contract nor any repository benchmark establishes that these alternatives are cheaper; reading less process state does not by itself prove lower runtime cost, and
PathAllocCanonicalizeallocates its result. Remove the performance claim and keep the property-based rationale.
//! One exists: `PathCchCanonicalizeEx`, or `PathAllocCanonicalize`. Either is
//! cheaper and reads no process state at all.
crates/windows-namespace-request-sys/DESIGN-NOTES.md:566
- The decision repeats an unverified performance claim: no direct comparison in this repository establishes that either lexical API is cheaper, and
PathAllocCanonicalizealso performs allocation. The keep/remove decision already rests on semantics, so state only that the alternatives omit current-directory rooting.
**The decision: keep `GetFullPathNameW`.** A genuinely lexical canonicalizer
exists -- `PathCchCanonicalizeEx`, or `PathAllocCanonicalize` -- and is cheaper,
reading no process state at all. It is the wrong call here, and for the property
crates/windows-platform-probes/src/request_cost.rs:83
- This propagates the unsupported claim that the lexical alternative is cheaper. The probe does not compare these APIs, and reduced process-state access is not a performance measurement; describe it simply as the lexical alternative.
//! [DESIGN-NOTES.md](../../windows-namespace-request-sys/DESIGN-NOTES.md) ->
//! `D-18`, which states what the call actually does, records keeping it over
//! the cheaper lexical alternative, and says plainly that whether it enters the
//! kernel is not established.
crates/windows-platform-probes/COMPLETED-CHECKLIST.md:103
- The resolved summary presents
PathCchCanonicalizeExas cheaper even though no comparison in this PR or the probe establishes that. Remove the cost claim here as well so this restatement matches the property-based decision.
`GetFullPathNameW` collapses `.`/`..` lexically but roots a path that is not fully qualified against
process state, so it is not a lexical call as a whole; `PathCchCanonicalizeEx` is genuinely lexical,
cheaper, and the wrong call, because rooting at submission is the property being bought. Whether it
crates/windows-namespace-request-sys/DESIGN-NOTES.md:537
- This adds a new canonical decision but no Tier 2 rationale. The repository design-document rule requires new decisions to be recorded in both
DESIGN-NOTES.mdandDESIGN-RATIONALE.mdin the same commit; this component has substantial design history, so add the rationale file/section and cross-link D-18.
## <a id="d-18"></a>D-18: `GetFullPathNameW` is not lexical, and the genuinely lexical alternative is the wrong call
crates/windows-namespace-request-sys/DESIGN-NOTES.md:560
path.rsis a repository file reference but is left as inline code. The markdown cross-reference rule requires references readers should open to be clickable relative links.
with two more in `path.rs`, two in [tests.rs](src/full_path/tests.rs), one in an
crates/windows-platform-probes/COMPLETED-CHECKLIST.md:97
- This archive entry does not follow the required completed-item structure: the date group must be an unanchored
## Moved ...heading, followed by a###item heading carrying them26anchor, the same summary as the stub, and the inline completion timestamp.
## <a id="m26"></a>Moved 2026-09-09 22:54:01 -04:00 -- M2.6: what `GetFullPathNameW` does, and whether it stays
crates/windows-platform-probes/CHECKLIST.md:92
- The completed-item rule requires the replacement stub to occupy one physical line. Keeping it on two lines defeats the compact active-checklist format used by the repository's existing completed stubs.
- [x] **M2.6** -- Say precisely what `GetFullPathNameW` does, in the crate that owns it, and decide
whether it is still the call `prepare` wants. -> [completed 2026-09-09](COMPLETED-CHECKLIST.md#m26)
- Files reviewed: 12/12 changed files
- Comments generated: 1
- Review effort level: Balanced
… own module
Fourth review round. Two findings, and they share a root cause: facts were
landing in whichever file was open rather than the file that owns them.
ROOTING IS NOT UNCONDITIONAL, AND FIVE SITES STILL SAID IT WAS. The previous
commit's own message called `CON` a counterexample to "roots a path that is
not fully qualified" -- then left that exact clause standing in path.rs, the
root DESIGN-NOTES, M26.5, D-18 and the probes archive, two of which it
rewrote in the same commit. The refuted statement outnumbered its correction
five to one. All now say "most paths".
THE DEVICE FORM IS LOOSER THAN "EXACT MATCH". Measured: `CON:`, `CON::`,
`NUL:`, `LPT1:`, `AUX:` map to the device namespace, as do `CON.`, `CON ` and
`con` -- the trailing colon is part of the form, the step-1 trimming runs
first, and matching is case-insensitive. Only a name with something after it
(`CON.txt`, `a\CON`, `CON:x`) roots normally. Saying "exact-match only" would
have told a reader `prepare("CON:")` gives a file path; it gives a device.
PLACEMENT. The `prepare("CON")` warning was in full_path.rs, which documents
`ResolveFullPath`; `prepare` lives in path.rs, whose own docs said nothing
about it. Moved. D-18, the durable record, mentioned neither the device
short-circuit nor the root-vs-drive distinction -- both existed only in a
module doc. Added.
Also mine, found while checking the reviewer's allocation claim: the cost
paragraph attributed "two heap allocations -- a copy of the input and a
MAX_PATH output buffer" to this crate while sitting in full_path.rs. That is
`path::resolve`'s shape, which is what the probe measures. `ResolveFullPath`
takes its input already owned and allocates one buffer per attempt, with a
growth loop `resolve` does not have. The paragraph now names which entry
point it is describing.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The new contract documentation has an internal sequencing contradiction and lacks required rationale separation and coverage for its platform-sensitive claims.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
crates/windows-namespace-request-sys/DESIGN-NOTES.md:537
- This new decision is recorded only in Tier 1 and embeds substantial draft history and alternatives. The repository convention requires new decisions to be written to both
DESIGN-NOTES.mdandDESIGN-RATIONALE.md; move the historical material (especially the draft chronology) to a new Tier 2 entry and keep D-18 focused on the current decision and constraints. See.github/copilot-instructions.md:1493-1512.
## <a id="d-18"></a>D-18: `GetFullPathNameW` is not lexical, and the genuinely lexical alternative is the wrong call
- Files reviewed: 12/12 changed files
- Comments generated: 2
- Review effort level: Balanced
…ontract, cover it with tests Addresses the Copilot review feedback on #86, plus a fifth-round finding. REMOVED THE "CHEAPER" CLAIM. Copilot flagged this in six separate reviews and was right every time: nothing here benchmarks PathCchCanonicalizeEx or PathAllocCanonicalize, Microsoft documents behaviour rather than relative cost, and PathAllocCanonicalize allocates its own result. It was a guess wearing the clothes of a measurement, in a decision whose entire subject is not doing that -- and it survived four rounds of review because I kept reading past it. The decision never needed it; rooting semantics decide it alone. Also notes that PATHCCH_ALLOW_LONG_PATHS makes PathCchCanonicalizeEx consult process long-path state, so "reads no process state" was wrong too. NARROWED "TOUCHES NO FILESYSTEM". What Microsoft documents is that the function does not verify the result is valid or names an existing file -- about verification, not I/O. Observation cannot close that gap: resolving under a directory that does not exist shows no CHECK was made, not that no filesystem was touched. Now stated as the documented guarantee at all seven sites, which is what the crate actually relies on. THE DEVICE SET WAS INCOMPLETE, AND SAID IT WAS CLOSED. COM/LPT accept SUPERSCRIPT digits: COM^1, COM^2, COM^3 (U+00B9/B2/B3) and the LPT forms all resolve to devices. My own earlier test appeared to show otherwise; re-run with explicit code points it was unambiguous, and the first result had been garbled console output I misread. Those are exactly the members a hand-written denylist omits, and path.rs tells callers to guard untrusted names -- so the doc now warns against building a filter from it. Also drops the claim that trimming happens before the device check, which was an undocumented ordering. TESTS, so none of this rests on prose again. Seven new cases pin every documented form: relative rooting, root-relative taking the ROOT (asserted as a relation, so it holds under any current directory), the device short-circuit, all its accepted spellings, the superscripts, the rooted-normally control, and fully-qualified invariance. Verified non-vacuous by injecting COM0 into the superscript case and watching it go red. CONVENTIONS. The M2.6 stub is one line; the archive has a date group plus an anchored item heading with a completion stamp; and D-18's draft chronology moves to a new Tier 2 DESIGN-RATIONALE.md, leaving Tier 1 the decision and the constraint it carries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Several summaries still omit the current-drive exception, and the PR description overstates whether the entry is consulted.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (5)
DESIGN-NOTES.md:1467
- This summary omits D-18’s current-drive exception:
X:foouses the process current directory whenXis the current drive; only another drive uses its recorded=X:entry. As written, this restates the contract too broadly.
but roots most paths that are not fully qualified against process state -- the
current directory, or for a drive-relative path the entry recorded for that
drive, which moves independently of it -- and that rooting is the property
CHECKLIST-thread-ambient.md:423
- The completed item still states the drive-relative rule without its current-drive arm. The new tests and D-18 show that the current drive uses the process directory and ignores the recorded entry; only another drive uses
=X:.
- [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 the entry recorded for that drive -- and never expands a
drive letter, so it
crates/windows-platform-probes/src/request_cost.rs:71
- This decomposition again makes every drive-relative path sound as though it reads the per-drive entry. D-18 and the added test establish a separate current-drive arm that uses the process current directory instead, so this probe documentation should preserve that distinction.
//! That call reads **process state** when it has to root a path -- the current
//! directory, or for a drive-relative path the entry recorded for that drive in
//! the `=C:` environment variables. **Neither sample here is rooted**: both are
crates/windows-namespace-request-sys/DESIGN-RATIONALE.md:110
- This historical summary says the final enumeration has three forms, but its drive-relative form still omits the measured current-drive branch. On the current drive the process directory wins; the recorded
=X:entry is used only for another drive.
- **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 takes the entry recorded for that drive
from `=C:`.
crates/windows-namespace-request-sys/src/full_path/tests.rs:552
full_path.rsexplicitly documents.\CONas rooting normally, but this exact-output test excludes it and the sibling negative test proves only that it does not start with\\.\. Returning.\CONunchanged would therefore pass both tests. Add an exact assertion for the collapsed-and-rooted result.
// `.\CON` is excluded deliberately -- the `.` component collapses, so its
// expected form is the bare name, which the loop below would have to
// special-case. It is covered by the device-negative test instead.
- Files reviewed: 22/22 changed files
- Comments generated: 2
- Review effort level: Balanced
…ng rule The rule "a drive-relative path takes the entry recorded for that drive" is stated in six places. The current-drive exception -- that the entry makes no difference there and the process directory wins -- had been added to exactly one of them, the owning crate's D-18, where it was discovered. The other five kept the broad form, so the contract read differently depending on which file a reader opened. That is this repository's blast-radius rule failing on the change that introduced the fact, not on inherited text. The measurement and the sweep landed in different commits, which is the gap the rule exists to close. Swept: the root DESIGN-NOTES corollary, CHECKLIST-thread-ambient M26.5, the cost probe's decomposition, and the Tier 2 enumeration of the rooting forms -- which also now records that "three forms" was itself one of the things stated more confidently than measured, the current-drive arm being a fourth correction to the same list. `path.rs` already had the narrow form and is unchanged. The owning crate's own wording moves too, from "the entry is ignored" to "makes no difference to the result", for the same reason the probe stopped saying "without consulting a device": an entry read and discarded is indistinguishable from one never read, and leaving one file saying "ignored" while another says "makes no difference" is the drift this sweep is meant to remove. Separately, `.\CON` was excluded from the exact-output device test on the grounds that the `.` collapses and it would need a special case. It needed one, so it has one. With the case left to the device-NEGATIVE test alone, the only claim made about `.\CON` was that it does not begin `\\.\` -- which an implementation returning it unchanged satisfies, and that too-weak assertion is the exact thing the exact-output test was written to strengthen. Measured: `.\CON` resolves to `<cwd>\CON`. Verified: 234 lib + 37 doc + 32 integration tests, clippy --all-targets, cargo fmt, encoding check over 630 files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
New filesystem-sensitive unit tests bypass the suite’s handle-allocation lock, creating races with raw-handle tests.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
crates/windows-namespace-request-sys/src/full_path/tests.rs:632
probe_directorycreates/removes directories, and its callers also runexists/is_fileand filesystem-querying drive-relative resolutions, but none takes the test suite's handle-allocation read lock. Concurrent raw-handle tests hold the write side specifically to prevent stale handle values from being reused (src/handle/tests.rs:25-31,263-274), so these helpers can invalidate that isolation. MakeProbeDirretain a read guard through itsDrop, or have every caller hold one for the complete probe lifetime.
fn probe_directory(tag: &str) -> ProbeDir {
// The full shape an accepted `=X:` entry must have, not just its first
crates/windows-namespace-request-sys/src/full_path/tests.rs:378
- This other-drive resolution is now documented and tested as consulting the filesystem, but the test does not participate in
handle_allocation(). It can therefore allocate a transient handle while a write-locked raw-handle test assumes no reuse is possible (src/handle/tests.rs:25-31,263-274). Acquire the read guard for the duration of this test.
fn a_drive_relative_path_is_rooted_at_that_drive_and_not_the_process_directory() {
// The third rooting form. A drive-relative path is rooted at *that drive's*
- Files reviewed: 22/22 changed files
- Comments generated: 1
- Review effort level: Balanced
…t claims
Five local review rounds, one per model, squashed. Each reviewer found something
the others could not see, and every finding is the branch's own defect class:
a claim stated more precisely than the evidence supports.
**A fixture that deleted directories it did not create.** `probe_directory`
called `create_dir_all` and set `created: true` unconditionally; `Drop` removes
the path on that flag. `create_dir_all` succeeds on an existing directory, so
the flag recorded a claim nothing checked -- and the name `%TEMP%\wnrs-<pid>-<tag>`
is one an interrupted run leaves behind and Windows can hand to a later process
on PID reuse. Measured: `create_dir_all` on an existing directory returns
`Ok(())` and establishes nothing, while `create_dir` returns `AlreadyExists`.
Ownership is now recorded only on a real creation.
**Two tests that could not observe what they claimed.** The UNC fixture ran AT
`\\localhost\C$`, where rooting `\foo` at the ROOT of the current directory and
at the WHOLE current directory give the same answer -- so the only coverage of
the UNC branch could not see the branch, for six commits. The child now runs one
level down, asserts it is below the share root, and pins the contrasting rule
alongside. And `CON:x` was covered only by `!starts_with("\\.\\")`, a predicate
the unrooted literal satisfies; it is now asserted against its full rooted
result, `<cwd>\CON:x`.
**A generalisation that measurement contradicts.** "Trimming applies per
component" was extrapolated from one case. Measured, position decides: the final
component loses any run of trailing dots and spaces, an intermediate component
loses a single trailing dot and nothing else -- `C:\a...\b` and `C:\a \b` come
back unchanged. The preservation cases were entirely absent, so an
implementation trimming every component passed the whole test.
**Three location kinds a sweep kept missing.** Round 17 corrected "not
consulted" by grepping `consulted`, which cannot match the same claim spelled
`ignored`: six sites survived, one an assertion failure MESSAGE. A later round
fixed a paragraph explaining that a heading reading "not an allocation" would be
contradictory, and left that exact HEADING one line above it. A third left the
per-drive qualifier out of an INLINE COMMENT sitting between a module doc and
emitted output that both had it. All swept, this time across every changed file.
**Two properties restated in prose and checked nowhere.** The per-test drive
letter lists were literals at six call sites, with disjointness and minimum
length asserted only in comments -- and an archived note enumerated five lists
after a sixth existed. They are now one table with a test enforcing both
properties; the note points at the table instead of listing letters.
**A harness that trusted its own marker.** The UNC test treated the presence of
an environment variable as proof it was the child it spawned, so a stray value
made the parent run the child's assertions in cargo's directory and blame the
crate. The marker now carries the directory the parent chose.
Also: a test name claiming two things its `ends_with` assertion cannot reach,
renamed; `PLANS.md` giving one checklist two incompatible states, corrected; and
`request_cost`'s cross-host ratio claim -- real, pre-existing on `main`, outside
this diff -- queued as `M2.9` rather than folded in.
Every fix verified by sabotage: the instrument was made to fail on purpose, then
restored. Every Windows claim measured by P/Invoke rather than argued.
Verified: 235 lib + 37 doc + 32 integration tests, the UNC test under
--include-ignored, clippy --all-targets, cargo fmt, workflow references, and the
encoding gate over 630 files.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The UNC assertion is case-sensitive for a case-insensitive path, and one newly documented device-path boundary remains untested.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 22/22 changed files
- Comments generated: 2
- Review effort level: Balanced
… it does not save Copilot asked for the documented `\CON` case to be pinned, since the module doc states 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 device names, `NUL` alone short-circuits as the final component of any path, however much path precedes it: input CON NUL X \\.\CON \\.\NUL \X Q:\CON \\.\NUL .\X Q:\...\CON \\.\NUL a\X Q:\...\a\CON \\.\NUL C:\X C:\CON \\.\NUL X.txt rooted rooted X:x rooted rooted `PRN`, `AUX`, `CONIN$`, `CONOUT$`, `COM1` and `LPT1` all behave as `CON` does. So a FULLY QUALIFIED path can still resolve to a device -- `prepare(r"C:\NUL")` is `\\.\NUL` -- and a caller treating a rooted path as evidence that a name refers to a file on that volume is wrong for this one name. Only a suffix takes it out. **Why this was invisible for twenty-two rounds.** Every earlier finding in this branch was a claim generalised from too few observations. This one was generalised from one MEMBER to a set: the doc enumerates the device set, then states the negative forms in terms of `CON`. The membership of that set was audited twice -- it is where the superscript `COM` spellings were found -- and the assumption that its members behave alike was never checked at all. Pinned by `nul_is_the_one_device_word_a_path_around_it_does_not_save`, which asserts the seven conforming names in both distinguishing forms and the four `NUL` spellings that reach the device. `\CON` is now asserted against its full rooted result, which is what was asked for. Verified by sabotage: replacing the NUL expectation with the general rule -- the exact rule the request implied -- fails on `\NUL`, `\\.\NUL` against `q:\NUL`. Swept into every restatement rather than the one file the finding named: `full_path.rs` (with the table), `path.rs`'s public summary, `D-18`, and the Tier 2 device-set entry. **Declined, with the measurement.** The second comment read the UNC assertion's case-sensitive comparison as a portability bug -- a host recording `LOCALHOST` would fail against the lowercase literal. It cannot: both sides descend from `UNC_ROOT`, since the parent builds the child's working directory from it. Measured -- a process started with `\\LOCALHOST\C$\Windows` reports exactly that from `current_dir()`, and likewise for `\\localhost\c$\Windows`, so Windows preserves the spelling it is given rather than canonicalising it. Recorded in place, with why the neighbouring precondition is case-insensitive for a different reason. Verified: 236 lib + 37 doc + 32 integration tests, clippy --all-targets, cargo fmt, encoding over 630 files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Some documentation still overstates what observation proves, one documented NUL boundary lacks coverage, and the enlarged test module needs splitting.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
crates/windows-namespace-request-sys/DESIGN-RATIONALE.md:37
- Changing the current directory and observing the same result proves output invariance, not that the implementation “reads no process state at all.” Keep this rationale at the observable boundary, consistently with D-18’s treatment of the current-drive entry.
crates/windows-namespace-request-sys/src/full_path.rs:39 - The examples here establish that the output is invariant under the current directory; they do not establish the internal claim that the call reads no process state. That repeats the mechanism-overreach D-18 warns about. State the observable lexical semantics instead unless there is implementation-level evidence for the read claim.
- Files reviewed: 22/22 changed files
- Comments generated: 2
- Review effort level: Balanced
| //! So a *fully qualified* path can still resolve to a device, which is the | ||
| //! part worth knowing: `prepare(r"C:\NUL")` yields `\\.\NUL`, and a caller | ||
| //! treating a rooted path as proof it names a file on that volume is wrong for | ||
| //! this one name. Only a suffix (`NUL.txt`, `NUL:x`) takes it out. |
… twenty-three rounds Copilot found the overreach in the half nobody was auditing. Every correction in this branch has been aimed at ROOTING -- "not consulted", "is ignored", "without consulting a device". The same error was sitting in the sentence doing the correcting: the LEXICAL half said collapsing `.`/`..` is "pure string work, reading no process state". Varying the current directory and getting the same answer shows the output does not DEPEND on that state. It does not show nothing was read -- which is exactly the argument this branch used to strike "not consulted" from the current-drive entry, applied to the positive claim instead of the negative one. Seven reviewers read that paragraph; it was invisible because it was the standard the other corrections were measured against. Both statements now say what the evidence reaches: the output is a function of the input alone. That is also all a caller needs, since it is what lets this half be reasoned about without knowing the process's state. **And the NUL escape boundary, which the doc named and the test half-covered.** The contract names `NUL.txt` and `NUL:x` as the spellings that take NUL out of the device namespace; the new test asserted only the root-relative `\NUL.txt`. Bare `NUL.txt` and `NUL:x` are now asserted, and so is the other side of the boundary -- `NUL::` and `NUL ` still reach the device, so the rule reads as "a suffix escapes" rather than "anything after NUL escapes". Measured: `NUL.txt` and `NUL:x` root under the current directory, `NUL::` and `NUL ` give `\\.\NUL`. Not taken here, and raised instead: this file is now 60.5 KiB, past the repository's 32 KiB "large" threshold and close to the 64 KiB one where a split becomes the default. The drive-entry fixture and its tests are a clean fracture point. A split is a pure relocation with its own commit and provenance trailers, so it does not belong inside a content change. Verified: 236 lib + 37 doc + 32 integration tests, clippy --all-targets, cargo fmt, encoding over 630 files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The expanded full-path test module needs splitting at its clear drive-entry fracture point under the repository’s large-module rule.
Review details
Suppressed comments (1)
crates/windows-namespace-request-sys/src/full_path/tests.rs:707
- This addition grows
src/full_path/tests.rsto 60.5 KiB and leaves two clear responsibilities in one large test module: device/rooting behavior and the process-environment/drive-entry fixtures below. The repository's source-module-size rule says large test modules should split readily at such fracture points; please extract the drive-entry tests and helpers into a focused child module (for example,src/full_path/tests/drive_entries.rs) before this file reaches the 64 KiB XL threshold.
/// A directory that exists, is in canonical `X:\...` form, and is neither a
/// drive root nor the process current directory.
///
- Files reviewed: 22/22 changed files
- Comments generated: 0 new
- Review effort level: Balanced
…ch layer the device rules describe Opening an older review thread surfaced that Copilot's review BODIES only list a subset of what it files -- twelve threads were open, several never seen here. Four were live. Two are substantive. **The probe fixture allocated handles outside the suite's serialisation.** `handle_allocation()`'s read guard means "I may open handles"; its write guard means "no other test may, while I reason about a specific handle value". Eleven tests across four modules take the write guard for exactly that. `probe_directory` called `create_dir`, `remove_dir`, `Path::exists` and `is_file` without the read guard -- and `std`'s Windows metadata goes through `CreateFileW` -- so those tests could run beside a write-guard holder and make its assertions fail for a reason unrelated to what it pins. The guard is now held by `ProbeDir` for its whole lifetime, which covers the fixture's own calls and the preconditions its tests run while it is alive. One older test doing a bare `Path::exists` takes it directly; it was the single filesystem call in the module the fixture does not cover. **A doc claim about the wrong layer, checked at both.** The review held that saying `CON.txt` and `a\CON` "do not reach a device" gives callers a false safety boundary, since Win32 reserves device basenames at open time. Measured at the open rather than the resolver: creating `<dir>\NUL` returns a `FILE_TYPE_CHAR` handle and leaves nothing on disk, while `<dir>\CON`, `<dir>\CON.txt` and `<dir>\NUL.txt` each create an ordinary `FILE_TYPE_DISK` file -- a real file named `CON` included. So the premise is false on this build: the reservation lives in ROOTING, the two layers agree, and `NUL` reaches the device at both. The valid half is kept anyway, because agreement measured on one build is not a guarantee this crate makes: the doc now says explicitly that those paragraphs describe the RESOLVER's output, and that a caller sanitising untrusted names should decide against what it will do with the result rather than infer open-time safety from a resolved spelling. Two stale restatements, both from earlier rounds of this branch: a comment still calling the drive-letter candidates a "disjoint pair" after they became three-element lists, and the probe's summary of the write-back stated unconditionally when an accepted entry is left alone. The PR description carried the same defect and is corrected in the same pass: it still said the current drive's entry is "neither consulted nor rewritten", the exact phrase the code moved away from, and now states the measured boundary and the NUL exception. Still open and not taken here: `full_path/tests.rs` is 62 KiB, past the 32 KiB "large" threshold and near the 64 KiB one. The drive-entry cluster is a clean fracture point, but a split is a pure relocation with its own commit and provenance trailers and does not belong inside a content change. Verified: 236 lib + 37 doc + 32 integration tests, clippy --all-targets, cargo fmt, encoding over 630 files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The enlarged test module needs splitting, and two checklist archive-format violations remain.
Review details
Suppressed comments (3)
crates/windows-platform-probes/COMPLETED-CHECKLIST.md:97
- This moved-group heading uses a timestamp, while archive groups use a date-only heading and reserve the precise timestamp for the anchored item heading below. Keeping the date-group format consistent prevents one completed item from introducing a separate grouping key.
## Moved 2026-09-09 22:54:01 -04:00 -- M2.6: what `GetFullPathNameW` does, and whether it stays
crates/windows-namespace-request-sys/src/full_path/tests.rs:908
- This material addition leaves
tests.rsat 63,791 bytes, just below the 64 KiB threshold, and the file now has clear test-specific fracture points (rooting/device behavior versus the=X:fixture and drive-entry cases). Split those responsibilities into child test modules now; keeping the whole 1,436-line test module together makes the next small change cross the XL boundary and makes these independent fixtures harder to maintain.
mod probe_drives {
CHECKLIST-thread-ambient.md:424
- This edited, checked item is still a multi-line historical body in the active checklist. Archive the completed M26.5 text and replace it here with the required one-line stub and stable item anchor, so the active checklist remains an action queue rather than retaining completed detail.
- [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
- Files reviewed: 22/22 changed files
- Comments generated: 0 new
- Review effort level: Balanced
…ive-entry arm Pure relocation, no behaviour change: 236 lib tests before and after, and `cargo fmt --check` is clean on the moved blocks. full_path/tests.rs had reached 62.3 KiB -- past this repository's 32 KiB "large" threshold and closing on the 64 KiB one where splitting becomes the default. The per-drive current-directory (`=X:`) cluster is the fracture point: it is the only part with a fixture of its own -- a probe directory, a drive-letter allocator, a reader and writer for the hidden entries, and a guard that restores a borrowed one -- and nothing outside it uses any of that. The two drive-relative ROOTING tests moved with the fixture rather than staying beside the other rooting forms, and that is a privacy constraint rather than a preference. A child module can see its ancestors' private items; a parent cannot see its child's. Left in the parent, those two tests could not reach the helpers, so the cut would not have been viable as drawn. They are also the two tests about this arm, so the constraint and the responsibility agree. The mass moved rather than the periphery: 21.8 KiB stays in the facade against 41.5 KiB extracted, so the parent is back under "large" and the child is a single coherent responsibility well clear of XL. Only 18 lines are additions -- the provenance header, the module doc, `use super::*;` and the `mod drive_entry;` declaration. Everything else is relocation, which is what lets `git blame -w -C1 -C1` attribute the moved lines to the commits that wrote them rather than to this one. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Split-Source: crates/windows-namespace-request-sys/src/full_path/tests.rs Split-Into: crates/windows-namespace-request-sys/src/full_path/tests/drive_entry.rs
There was a problem hiding this comment.
🟡 Changes recommended
A device-mapping assertion remains too weak, and several documentation references or contract restatements are stale.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
crates/windows-namespace-request-sys/COMPLETED-CHECKLIST.md:44
- The candidate table and its enforcing test were extracted to
src/full_path/tests/drive_entry.rs; this link now opens the facade rather than the inventory described here.
live in one table in [tests.rs](src/full_path/tests.rs) with a test enforcing
- Files reviewed: 23/23 changed files
- Comments generated: 4
- Review effort level: Balanced
…point the links the split moved Copilot's review of the split. Its two main asks were already done -- the split itself, and the NUL suffix cases -- so the live findings are what it saw in the new shape. **A device assertion that only proved "some device".** `a_legacy_device_name_short_circuits_rooting` asserted `resolved.starts_with(r"\\.\")` over eight names, which would pass if `CON` resolved to `\\.\NUL` -- and the contract names which device each spelling reaches. Both device tests now assert the complete mapping. That also pins what the device form does to a spelling: `CON:`, `CON::`, `CON.` and `CON ` all give `\\.\CON`, while `con` and `cOn:` give `\\.\con` and `\\.\cOn`, so trailing punctuation is dropped and case is carried through. Verified by sabotage -- expecting `\\.\NUL` for every name, which is Copilot's own scenario, now fails on `CON`. **Three links the split invalidated**, all mine: the archive's pointers to the verbatim test and the candidate table, and Tier 2's pointer to the empty-entry regression test, all now resolve to `drive_entry.rs`. Two others were checked and left -- they cite tests that stayed in the facade. Also: the probes archive stated the `=X:` write-back unconditionally, where an accepted entry is left alone; and the `## Moved` group heading this branch added carried a timestamp where the format reserves that for the anchored item heading. **Declined, and queued as `M-inf.2` instead.** A comment asked for M26.5's completed body in `CHECKLIST-thread-ambient.md` to become a one-line stub. The rule is right and the file does violate it, but M26.5 is not exceptional -- stubbing only the reported item would have made it inconsistent with the five siblings written the same way. Counted rather than assumed: every group in that file is complete and due for migration -- M22 (8 items), M23 (6), M24 (6), M25 (7), M26 (6), M27 (6), M28 (4), M29 (5) -- with only `M26+` open. That is roughly 400 lines of another feature's bookkeeping, and moving it through a `GetFullPathNameW` documentation branch would bury the change this branch exists to make. Verified: 236 lib + 37 doc + 32 integration tests, clippy --all-targets, cargo fmt, encoding over 631 files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Peels the
GetFullPathNameWcorrection ("G") out of #56, and completes M2.6.This description has been rewritten. The version it replaces asserted, in bold, that "touches no filesystem" is the claim that holds -- which this PR went on to measure false. Leaving that standing while the diff below disproves it would have been one more instance of the defect the PR is about, so the record is corrected here rather than only in the code.
The defect
full_path.rs said, in two consecutive sentences:
Those disagree. A lexical canonicalizer is a pure function of its input; this reads mutable process state -- the current directory, and for a drive-relative path such as
C:foothe per-drive current directory Windows keeps in the hidden=C:environment variables.That was the entry point. The subject turned out not to be one wrong word.
What the call actually does
Measured by P/Invoke, not read off documentation:
./..is pure string work that reads no process state (C:\a\..\bisC:\bunder any current directory). Only rooting reads process state.X:foofor a non-current drive validates the=X:entry against both a shape and existence -- an existing file is rejected, as is a missing directory,C:/...,...\.,...\..\...and\\?\...-- and an entry that fails is written back as the drive root, created when absent, so this happens on a pristine host on every call. An accepted entry is used verbatim, even onto another drive, and literally:C:\Windows\yieldsC:\Windows\\foo.NULdoes so even from a fully qualified path (C:\NULresolves to\\.\NUL, where every other device word roots normally once anything precedes it).CON,CON:,CON.,con,CONIN$andCOM/LPTplus one digit -- including the superscripts U+00B9/B2/B3, which a hand-written denylist misses.build - clonegap (~165 ns) is an upper bound spanning one net allocation and a builder chain; the call alone measures ~110 ns.The decision, recorded as D-18
A genuinely lexical canonicalizer exists --
PathCchCanonicalizeEx, orPathAllocCanonicalize.It is the wrong call here, and the reason is the property rather than the price. Resolving against the current directory at submission is what this crate is buying. A lexical canonicalizer would leave a relative path relative, so its meaning would be settled on the worker at execution time, against a directory any thread may have changed in between -- reintroducing exactly the race preparation exists to close. No cost claim is made about the alternatives, because none was measured.
Whether it enters the kernel is still not established, and the earlier argument for that -- "both are ordinary process memory" -- has been withdrawn. The reasoning was sound and the conclusion was false: the drive-relative form queries the filesystem and writes the environment block.
The real subject: claims that outrun their evidence
The PR ran to fourteen review rounds, and nearly every one found the fix for the previous round committing a fresh instance of the same error. That history is now the worked example, recorded in DESIGN-RATIONALE.md: nine successive wrong drafts, each naming a mechanism the evidence did not reach.
The sharpest instance is not about
GetFullPathNameWat all. A test helper folded "empty=X:value" into "absent entry", in a comment that called the equivalence measured. The measurement had never cleared the last error -- the only thing that distinguishes them -- so it could read nothing but what an earlier call left behind. Cleared and re-measured, they are distinct. The consequence was live: the guard that restores a borrowed entry on unwind deleted an inherited empty one, destroying the state it exists to preserve.So: "measured" is itself a claim, and a procedure can be wrong in ways its result never reveals.
The sweep
M2.6 named one file. Sweeping found ten statements of the same fact in six files -- including one in a file an earlier commit had already edited, and one used as a live premise inside an open milestone in the root CHECKLIST. The reported site is a sample, not the population.
Deliberately not touched: the 2026-08-27 design session uses "lexical canonicalisation" generically and correctly, and a Tier 3 transcript is a raw record rather than a normative statement.
Scope
27 commits. Not documentation-only, and the earlier claim that it was is corrected here: the branch adds executable tests and a
[dev-dependencies] windows-sysentry (Win32_Foundation,Win32_System_Environment) needed becausestd::envcannot address a key containing=. Commit types are scoped accordingly --docs,test, and twofixcommits inwindows-namespace-request-sysplus one in the probes crate. The library's public API and behaviour are unchanged; every code change is in tests, comments, or the probe's report text.234 lib tests, 37 doctests, 20 + 11 acceptance tests, and 158 probe tests pass; clippy
--all-targets, rustfmt, rustdoc and the encoding gate over 629 files are clean.