Skip to content

Derive a file's flush domains at its open (M39) - #182

Merged
MikeGrier merged 19 commits into
mainfrom
mikegrier/m39-1
Nov 6, 2026
Merged

MikeGrier merged 19 commits into
mainfrom
mikegrier/m39-1

Conversation

@MikeGrier

Copy link
Copy Markdown
Owner

Completes root M39: flush domains for files opened through the namespace route. A file win-durable-ioring opens now carries the disks of its volume as its flush domains, so failure scoping follows the storage without the consumer declaring it.

What changed

  • win-volume-sys (feat): storage_device_descriptor and StorageBusType, a disk's device descriptor (StorageDeviceProperty). BusTypeNvmeof (20) is named from the documentation page, since windows-sys 0.61.2 lacks it.
  • win-durable-ioring (feat!): a file's flush domains are derived at its open, by path or by identifier, in OpenedFile::admit (DI-D-117):
    • the handle's volume, then its disk extents, then disk_flush_domain for each disk, the same keys a raw volume derives;
    • a virtual disk is detected by its documented bus type and followed to the volume holding its backing file;
    • all or nothing: any failed step leaves the file unknown, the safe default;
    • a file on a share is unknown, by the engineer's choice (no key for a share, since a server's name is not its identity).
  • Breaking change. Win32FileTarget::new now reports the domains found as well as those declared, so files on one disk share fate. with_domains adds and never removes.
  • DirectoryDomains::predict and confirm (DI-D-118): predict a directory's domains from its own handle, then check a file's against the prediction, which catches a junction swapped between the two.
  • Recorded decisions:
    • WV-D-13: a disk's write cache gets no decoded entry.
    • DI-D-117's reserved opt-out: its shape is reserved and it isn't built.

Tests

  • Derivation: the rule is tested against an in-memory storage stack (virtual disks, nesting, a loop, the bound, a failure at each step), plus real local files.
  • Prediction: a directory predicts its volume's disks, a file created in it confirms, mismatches report both sides, and a step that fails is an error rather than an empty prediction.
  • End to end: a data file and a log file, where a failure of the data file's storage reaches the log exactly when they share a disk. The separate case only runs where the log directory is on another disk.
  • Existing tests: four tests that stated the old rule now state the new one. The dioring tests set a temporary file's found disk aside through a test-only seam, because they use files on one disk to stand for separate devices.
  • Sabotage: 7 entries in win-volume-sys, and 13 plus a control in win-durable-ioring, all behave as declared. Two older entries were repointed.

Not yet run anywhere

These need an administrator and run on CI only:

  • a real virtual disk's bus type and storage dependency;
  • a file on a formatted scratch virtual disk sharing fate with its host volume.

Both are wired into the CI step that already attaches scratch disks. The loopback-share tests run in the existing admin-share step.

Mike Grier and others added 14 commits November 5, 2026 16:21
The engineer, 2026-11-05: a file win-durable-ioring opens has its flush domains derived at the open, in OpenedFile::admit, for opens by path and by identifier, all or nothing. At the open because the core reads a target's domains through an accessor once when it adds the target, blocking work belongs in the open a consumer already schedules, and a raw volume already reads its extents there; an open by identifier gets all of it, since every step reads the handle. An opt-out's shape is reserved and not built: a request method taking a non_exhaustive enum whose default derives. The texts the decision amends change with the code, in M39.2.

Completed item: M39.1: Record the decision, and settle its two open choices.

Co-authored-by: Copilot <[email protected]>
Each candidate's page was read on 2026-11-05. GetDriveTypeW is not wrapped: nothing in the derivation acts on a drive's kind. GetVolumePathNameW and GetVolumeNameForVolumeMountPointW are not needed by M39.2, which reads the opened handle; they move to M39.3, whose prediction before a file exists may want them. NetDfsGetClientInfo (the plan's DfsGetClientInfo) is needed only if M39.2.3 keys a remote file by server, so it moves there. Nothing is wrapped by this item.

Completed item: M39.2.1: Wrap the name-based calls the derivation needs.

Co-authored-by: Copilot <[email protected]>
… type

Found while starting M39.2.2: M39.2.4's all-or-nothing rule and the texts DI-D-117 amends cannot land apart from the first code that derives a domain, since that code is what makes the old texts false, so the two items are one. Also: the GetStorageDependencyInformation page (read 2026-11-05) names no error for a volume that is not on a virtual disk, so the measured 0xC03A0015 is not built on; a disk is followed through its dependency when its documented bus type is BusTypeFileBackedVirtual, which needs a win-volume-sys entry.

Co-authored-by: Copilot <[email protected]>
…descriptor and bus type

IOCTL_STORAGE_QUERY_PROPERTY for StorageDeviceProperty, decoded as STORAGE_DEVICE_DESCRIPTOR: the SCSI device type and modifier, removable media, command queueing, the bus type as a raw-carrying StorageBusType that names the values the STORAGE_BUS_TYPE page lists (BusTypeNvmeof, 20, from the page, since windows-sys 0.61.2 does not declare it) and keeps any other, the four optional strings as bytes, and the bus-specific bytes. A string or the bus-specific bytes outside the reply, a string starting inside the fixed fields, or one with no terminator is InvalidData. Root M39.2.2 reads the bus type to decide whether a disk is followed to the file that backs it.

Tests: a real disk, whose version is the structure's size as the page says, every disk a volume is on, the first-buffer sizes, plain and overlapped handles, a handle that is not a disk, and built replies in both directions. Seven sabotage entries, all caught.

Co-authored-by: Copilot <[email protected]>
…dle when it is opened

DI-D-117 (root M39.2.2). A file this crate opens, by path or by identifier, has its flush domains found in OpenedFile::admit: the handle's final path in GUID form names its volume, the volume's disk extents give its disks, and each disk is disk_flush_domain, the bytes a raw volume derives. A disk whose bus type is BusTypeFileBackedVirtual is followed through the volume's storage dependency to the volume holding its backing file. All or nothing: any step that fails, as it does for a file on a share, leaves none found, which is unknown and the safe default. OpenedFile::flush_domains reports them and Win32FileTarget::new carries them; with_domains adds to them and never removes one.

BREAKING CHANGE: a file target's domains were exactly what the consumer declared, and now include the disks of the file's volume. Failure scoping follows: files on one disk share fate, as their disk does.

Tests: the rule against a storage stack in memory (virtual disks, nesting, several hosts, a loop, the bound, a failure at each step), real local files, both open modes, a declaration adding to what was found, a file over the loopback share finding none, and a scratch virtual disk on CI observing the hop. Four tests that restated the old rule now state the new one; the dioring tests set a temporary file's found disk aside through a test-only seam. Nine sabotage entries and a control, two older entries repointed; all behave as declared.

Completed item: M39.2.2: A local file's domains, all or nothing, with the texts that change with them.

Co-authored-by: Copilot <[email protected]>
… DI-D-117 records the bus-type basis

API.md section 13 called deriving flush domains from Windows' storage topology a locality helper's job; it now says win-durable-ioring derives a file's from its handle when it opens it and a raw volume's from its disks, and a target made any other way declares its own. DI-D-117 records that a virtual disk is decided by its STORAGE_DEVICE_DESCRIPTOR bus type, since the storage dependency query's page names no error for a volume that is not on one.

Co-authored-by: Copilot <[email protected]>
…ey is derived for it

The engineer, 2026-11-05, choosing among unknown, one key for every remote file, and a key per server. A server's name is not its identity, clustered servers share disks, a DFS link hides its target, and a share can be this machine's own disk, so any key could split two files that share fate. A consumer that knows a share's storage declares it.

Co-authored-by: Copilot <[email protected]>
…and a declaration is its whole set

DI-D-117 (root M39.2.3, the engineer's choice "a"): no key is derived for a share. Win32FileTarget's documentation says so and why, and that with none found what a consumer declares is the file's whole set; the loopback-share test now asserts that too. No behaviour changes: M39.2.2's derivation already found none for a remote handle.

Completed item: M39.2.3: A remote file's domain.

Co-authored-by: Copilot <[email protected]>
Completed item: M39.2: Derive a file's found domains at its open, all or nothing.

Co-authored-by: Copilot <[email protected]>
…firm a file's against the prediction

DI-D-118 (root M39.3, the engineer's choice of the directory's own handle). DirectoryDomains::predict opens the directory through windows-namespace-request-sys' OpenFile with no access and FILE_FLAG_BACKUP_SEMANTICS, reusing open_request, and runs the same derivation a file's open runs on that handle, so the prediction and a file's domains are one rule and the same bytes. A prediction is never a declaration: confirm compares a file's own domains with it and returns a DomainMismatch carrying both, which is how a junction swapped between prediction and create is noticed. A prediction that cannot be made, as for a directory on a share, is an error with its reason, never an empty set.

Tests: a directory's prediction against its volume, a file created in it confirming, two kinds of mismatch, a missing directory, a failing stack, and over the loopback share (ignored; named so the existing CI step runs it). Four sabotage entries, all caught.

Completed item: M39.3: Predict a directory's domains before a file is created, and confirm them at the open.

Co-authored-by: Copilot <[email protected]>
…cted from its own handle

The engineer, 2026-11-05, choosing the directory's own handle over resolving its name: one derivation, the same bytes, and no second statement of which volume a path is on. The probe handle is not a target, so DI-D-64's ruling on a target's open options stands. A prediction is never a declaration, a mismatch is reported with both sides, and a prediction that cannot be made is an error.

Co-authored-by: Copilot <[email protected]>
…ved flush domains

Root M39.4. A data directory and a log directory, each predicted and its file confirmed, both files opened through the route with their domains derived, a write to each, and a failure imported over the data file's domains: the data write is suspect, and the log's exactly when the files share a disk, which case it was being printed (the separate case runs only where the log directory's volume is on another disk, such as a CI runner's work disk). An import stands in for a failed flush since two flushes in one seal complete in either order. On CI only, a file on a formatted scratch virtual disk shares fate with a file on the volume holding the disk's file, through the virtual-disk hop; ScratchVolume gains create_formatted, and the CI step that attaches scratch disks runs the new test.

Completed item: M39.4: Show it end to end, in the shape of the working model.

Co-authored-by: Copilot <[email protected]>
…hived M39

Root M39 is complete and archived; the decisions' links to it and their scheduled-work lines now point at the archive.

Co-authored-by: Copilot <[email protected]>
Every M39 item was already archived under its own heading; the section and its stubs leave CHECKLIST.md, and one milestone entry in COMPLETED-CHECKLIST.md says what it delivered and links its items and decisions. PLANS.md drops its M39 sentence.

Co-authored-by: Copilot <[email protected]>
Copilot AI balanced review requested due to automatic review settings November 6, 2026 00:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Raw virtual targets omit host-disk dependencies, so failed commits no longer mark affected host-file writes suspect, violating the durability contract.

2 open issues
What changed in this PR

This PR derives flush domains when win-durable-ioring opens files through the namespace route, allowing failure scoping to follow their underlying storage without consumer declarations.

Changes:

  • Adds storage-device descriptors and bus types to win-volume-sys.
  • Derives file domains through volume disks and virtual-disk dependencies, with directory prediction and confirmation.
  • Updates tests, sabotage cases, CI coverage, and M39 documentation.
File Description
PLANS.md Removes M39 from active work.
crates/​win-volume-sys/​src/​storage_device/​tests.rs Tests descriptor decoding and disk queries.
crates/​win-volume-sys/​src/​storage_device.rs Adds device descriptors and bus types.
crates/​win-volume-sys/​src/​lib.rs Exports the descriptor API.
crates/​win-volume-sys/​sabotage.json Adds descriptor sabotage cases.
crates/​win-volume-sys/​README.md Documents device queries.
crates/​win-durable-ioring/​src/​windows_backend/​tests.rs Updates file-domain expectations.
crates/​win-durable-ioring/​src/​windows_backend/​opened_volume/​tests.rs Checks found and declared file domains.
crates/​win-durable-ioring/​src/​windows_backend/​opened_file/​open_request.rs Shares open helpers within the backend.
crates/​win-durable-ioring/​src/​windows_backend/​opened_file.rs Derives and stores domains at admission.
crates/​win-durable-ioring/​src/​windows_backend/​file_target.rs Carries found domains into targets.
crates/​win-durable-ioring/​src/​windows_backend/​file_domains/​tests.rs Tests derivation and storage dependencies.
crates/​win-durable-ioring/​src/​windows_backend/​file_domains.rs Implements all-or-nothing domain discovery.
crates/​win-durable-ioring/​src/​windows_backend/​directory_domains/​tests.rs Tests predictions and mismatches.
crates/​win-durable-ioring/​src/​windows_backend/​directory_domains.rs Adds directory prediction and confirmation.
crates/​win-durable-ioring/​src/​windows_backend.rs Exports directory-domain APIs.
crates/​win-durable-ioring/​src/​scratch_volume/​tests.rs Tests the formatting script.
crates/​win-durable-ioring/​src/​scratch_volume.rs Supports formatted scratch volumes.
crates/​win-durable-ioring/​src/​dioring/​tests/​working_model.rs Exercises storage-based failure scoping.
crates/​win-durable-ioring/​src/​dioring/​tests.rs Preserves synthetic test-domain isolation.
crates/​win-durable-ioring/​sabotage.json Adds derivation and prediction mutations.
crates/​win-durable-ioring/​README.md Explains automatic domains and prediction.
crates/​durable-ioring/​DESIGN-NOTES.md Records open-time derivation decisions.
crates/​durable-ioring/​API.md Clarifies which layer derives domains.
COMPLETED-CHECKLIST.md Archives M39 implementation work.
CHECKLIST.md Removes archived M39 items.
.github/​workflows/​ci.yml Includes share and virtual-disk tests.

Review effort: Balanced

…ation

The rustdoc job denies broken intra-doc links: `derive` named both the function and the built-in attribute macro, so it now says `fn@derive`; and a link whose target was its own text is written bare.

Co-authored-by: Copilot <[email protected]>
Copilot AI balanced review requested due to automatic review settings November 6, 2026 00:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Whole-path Unicode conversion rejects valid Windows names containing unpaired UTF-16 surrogates, leaving file domains unknown and causing directory-domain prediction to fail despite a valid volume-GUID prefix.

2 open issues
1 previously missed issue

In code that hasn't changed since last review

Low severity Include domain values in mismatch errors

crates/​win-durable-ioring/​src/​windows_backend/​directory_domains.rs:125

A mismatch between two different single-disk volumes is displayed as predicted 1, found 1, so the error text hides which domains actually differ. Include the domain values rather than only their counts so a caller logging or propagating this error can identify both sides of the mismatch.

Review effort: Balanced

Mike Grier and others added 2 commits November 5, 2026 20:25
… domains

Addresses the review of PR #182.

A raw volume or raw disk on a virtual disk carried only its own disk, while a
file beside the disk's backing file carried the host's disks, so a failed
commit on the raw target did not make the file's writes suspect.
OpenedVolume::open and OpenedDisk::open now derive their domains at the open
by the walk a file's use: a volume from its own disks and its own storage
dependency, a disk through GET_STORAGE_DEPENDENCY_FLAG_DISK_HANDLE on its own
handle. All or nothing, as for a file. Windows::disks_of no longer opens an
OpenedVolume, which would now recurse.

A final path or host volume name holding a lone UTF-16 surrogate no longer
fails the derivation: only the volume prefix is decoded.

DomainMismatch's message carries both sets of domains, not their counts.

Tests: the derivation from a volume and from a disk in memory; files and
directories named with a lone surrogate; the mismatch message; and, for an
administrator on CI, a raw volume and a raw disk on a VHD carrying the host's
domains and sharing a failure with a file on the host. Sabotage: four new
entries caught; two repointed and caught.

Co-authored-by: Copilot <[email protected]>
Records the PR #182 review fix: a raw target on a virtual disk derives the host's
disks at its open, all or nothing, by the walk a file's use.

Co-authored-by: Copilot <[email protected]>
Copilot AI balanced review requested due to automatic review settings November 6, 2026 01:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Drive-letter reassignment can produce incorrect failure domains for the retained volume handle, and the new raw-target tests panic before validating failure scoping.

4 open issues
1 resolved since last review

Review effort: Balanced

Mike Grier and others added 2 commits November 5, 2026 20:42
…d handle

Second review round on PR #182.

- OpenedVolume::open seeds the walk from the extents its own handle reported
  and asks that handle for its storage dependency, instead of re-resolving
  the path, which a drive letter reassigned since the open would send to
  another volume. OpenedDisk::open asks its own handle the same way. The
  walk's entry for raw targets is file_domains::seeded; StorageStack loses
  hosts_of_disk, and only host volumes found are opened by name.
- The raw-target working-model tests write one sector each from AlignedBuf
  through an aligned instance; the Vec<u8> harness failed the unbuffered
  target's sector check before any scoping assertion ran.
- host_flush_domains numbers its file with NEXT, so tests running at once
  in one process do not share it.

Sabotage: two entries repointed at seeded, two added (a plain-disk target
asks its handle for hosts; a raw volume's own disks not taken from its
extents); all four caught.

Co-authored-by: Copilot <[email protected]>
…andle

Records the second review round on PR #182: a raw volume's or disk's domains
are seeded from the handle its open retains, never from its path again.

Co-authored-by: Copilot <[email protected]>
Copilot AI balanced review requested due to automatic review settings November 6, 2026 01:42
@MikeGrier

Copy link
Copy Markdown
Owner Author

Review rounds 1 and 2 (5794349046, 5794565662) -- all findings changed, none declined.

Round 1 (6b678e4, docs 888184b):

  • Raw volume/disk on a virtual disk lacked the host's domains: both opens now derive by the file walk; disks via GET_STORAGE_DEPENDENCY_FLAG_DISK_HANDLE.
  • Lone UTF-16 surrogate broke volume_prefix/host_volume: they read only the prefix of the UTF-16 path.
  • DomainMismatch's message shows both domain sets.

Round 2 (b119ce3, docs 7148dbb):

  • Raw targets' domains are seeded from the retained handle (extents + that handle's dependency query), never the path again; new file_domains::seeded, StorageStack::hosts_of_disk removed.
  • Raw-target working-model tests run an aligned instance with sector-sized AlignedBuf writes.
  • host_flush_domains file name carries the NEXT counter.

What the rounds taught: round 1's fix took the right walk but re-entered it by name, the same "re-resolve rather than use what the open holds" defect the open-time derivation exists to avoid; and its tests were written against the cached Vec harness, which no raw unbuffered target can pass, so they had only ever run on CI's admin step. Sabotage: 2 entries repointed, 2 added, all caught. Lib tests, clippy, and rustdoc with CI flags pass locally; the raw tests run on CI's admin step.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Administrator-only Windows tests remain unrun for virtual-disk detection, storage dependencies, and shared flush-domain behavior, and pending CI results still need review.

No open issues 🎉

4 resolved since last review

Review effort: Balanced

@MikeGrier
MikeGrier merged commit 2b69902 into main Nov 6, 2026
44 checks passed
@MikeGrier
MikeGrier deleted the mikegrier/m39-1 branch November 6, 2026 01:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants