Windows selected-file and diagnostic adapters
The storage service uses a dedicated Windows adapter for explicitly selected ordinary files. It can take bounded snapshots and verified reads, create new backup files, move one reviewed file into quarantine or back to its original scope, and purge one explicitly approved file. The coordinator remains responsible for the owner's actual authorization, protected-location policy, recovery requirements and durable operation receipts. Calling a lower-level function is not evidence of consent.
scripts/Windows.Native.cs supports cleanup of owned assessment records. The selected-file adapter uses its own checked contract for explicitly selected roots; the assessment-record cleanup allowlist does not authorize unrelated paths.
On this page
First Windows session
The workflow CLI with no arguments, or a bare PatchCairn: start, displays the menu without allocating a session or collecting host information. Choosing a job with --mode real binds the new session to the detected native platform before private storage is allocated; Windows is detected from the runtime platform identifier, without inventory collection. An explicitly different or unknown real platform, or an unsupported host, is refused before session allocation. Fixture mode remains the safe CLI default and may use unknown for pure state tests; explicitly select --platform windows for Windows adapter fixtures. Creation mode and platform remain immutable, so a fixture never becomes a real session. Actual scope authorization, protected consent and the exact reviewed plan are still separate requirements.
Use a reasonably short local extraction folder: private Windows records and selected-file operations keep the conservative 240-character full-path bound. Leave room for generated session/request names and the fixed private snapshot path work/windows-scoped/worker-<32hex>/Windows.Scoped.Dispatch.ps1; a selected root fitting the cap alone is insufficient. A menu can display successfully while the first private record or worker startup holds because its full path is too long. Explain the hold before choosing a shorter location for a fresh session; do not silently move, adopt or promote existing records.
Runtime and native boundary
scripts/scoped_fs.py delegates Windows calls to scripts/windows_scoped.py, with the owned transport in scripts/windows_worker.py. The supported worker route requires Windows 10 or Windows Server 2016 or later, 64-bit Python 3.12+, and an existing 64-bit Windows PowerShell 5.1 or PowerShell 7 host in FullLanguage mode with Add-Type available. The parent Python bitness and the child readiness bitness/language checks are separate. Required creation-time job membership, explicit handle inheritance and nested-job support must actually be available; an unsupported host holds before request delivery.
The Python helper lazily starts only the fixed private dispatcher snapshot through native CreateProcessW, with an exact executable and quoted argument array. It uses no shell and takes no executable command, script block, compiler option or expression from a plan or JSON record. Execution policy, constrained language, access controls, antivirus and other OS restrictions remain effective; no bypass or elevation request is supplied.
Before writing any request bytes or launching PowerShell, each call creates or admits work/windows-scoped through scripts/private_workspace.py. On Windows, the helper uses the actual current-process token user and native security APIs to create a protected inheritable DACL allowing only that user, SYSTEM and Administrators. It reads back the real owner, DACL, ordinary-file type and persistent-ACL capability. Broad or unsupported existing namespaces cause a hold; the helper does not change existing ACLs, take ownership or adopt an old permissive directory. Ordinary shared repository/work ancestors may remain shared. Their handles and the private namespace/request handles stay pinned throughout dispatch and response persistence.
Every logical request retains new exclusive request.json and response.json files under work/windows-scoped/request-<id>/. The parent admits the private directory and actual file owner/DACL before private bytes, hashes and reads back the exact request bytes, and holds no-write/no-delete-sharing pins through exchange and response persistence. The worker confines the request reference to its exact startup root and matching ID, then verifies the bounded packet bytes against that hash. Requests may contain Base64 input bytes, and read responses may contain an entire selected file; Base64 is not encryption.
Each cold generation also creates work/windows-scoped/worker-<generation>/ for the fixed dispatcher/C# source snapshots and compiler TEMP/TMP. The parent binds those source bytes to the generation and retains ordinary-ancestry and read-only-sharing pins while they are used. The worker verifies the source hashes and compiles those same C# bytes once. Worker snapshots, compiler artifacts and request/response copies remain private retained artifacts; shutdown or recycling deletes none of them. Existing assessment-run cleanup does not automatically own these directories. Tests use new owned private fixture children. None of these content-bearing files belongs in a source export. PowerShell sets process TZ to UTC without changing the operating system time zone.
New files created by the selected-file C# adapter also receive an explicit creation-time descriptor owned by that process's actual token user, with a protected DACL restricted to that user, SYSTEM and Administrators. The adapter reads back the owner and exact DACL before its first payload write and after flushing. This preserves strict current-user admission even when a process token's default owner is an administrator group. It changes no existing owner or ACL and does not require elevation.
This change does not make historical artifacts private. An old namespace with broader permissions is refused before a request is written or the dispatcher starts. Its files are not scanned, deleted, moved or repaired automatically; their separate disposition belongs to the owner. SYSTEM, Administrators and the same user remain outside this confidentiality boundary.
On Linux the shared private-artifact helper requires actual effective-user ownership and 0700 directories/0600 single-link ordinary files, plus a read-only native-filesystem gate before allocation and on actual objects before use or payload bytes. The currently reviewed gate supports glibc Linux x86-64 LP64 and allows ext2/3/4, XFS, Btrfs, tmpfs and ramfs. Unknown, foreign, network, FUSE, NTFS and 9p/DrvFS filesystems hold, even if Linux metadata reports 0700/0600. It does not reject WSL categorically: a supported native Linux filesystem can pass. This performs no host-ACL bridge, mount/configuration change or permission repair.
Darwin admission requires actual effective-user ownership and 0700 directories/0600 single-link ordinary files, plus a fixed read-only filesystem check on the existing allocation parent and actual private object. Only reviewed writable local APFS/HFS variants with ownership enforcement enabled can pass; an ignore-ownership volume, unsupported filesystem or native interface, or a query error holds before private payload bytes. The separate extended-ACL checker permits only a source-verified absent ACL property on the same checked object or a valid empty ACL. Any ACL entry or unsupported query state is a hold. These native interfaces and return states have mocked coverage and primary-source review; actual private-storage and file-operation fixtures also passed on hosted macOS ARM64. The platform evidence records the tested context; unsupported states and other filesystem/ABI variants remain outside that native result. See the filesystem contract for the exact platform limits.
The native implementation accepts existing selected roots on fixed local NTFS volumes. It refuses drive roots, UNC/network paths, device and extended-path syntax, paths over its conservative 240-character native bound, alternate data streams, ambiguous components, and reserved DOS names. Other filesystems and an unavailable volume capability produce a hold. The same-drive requirement for a move is deliberately stricter than detecting every possible volume alias.
CreateFileW opens every ordinary ancestor from the drive root through the selected directory, and then the selected file, with reparse-point processing disabled. Every opened handle is checked against the requested final path, disk-file type and native metadata. Directory handles prevent their deletion or rename during the operation; file sharing prevents new writers and deleters while a file is being checked. Reparse points, files with multiple hard links, offline/recall files and encrypted files are refused. Mutation additionally refuses read-only and system files. The implementation never takes ownership, changes an ACL, clears protective attributes or enables token privileges.
The adapter compares volume serial, file ID, size, write time, creation time, link count and attributes, and requires a fresh SHA-256 match for content operations. Windows records use the volume serial as device, a hexadecimal native file ID as inode, native attributes as mode and attributes, and creation time as ctime_ns. These fields are adapter-specific and must not be interpreted as POSIX permission bits or POSIX change time.
For a move, the source and destination ancestors are held together. SetFileInformationByHandle renames the actual checked source handle with replacement disabled; it then verifies the destination path, identity and content. There is no cross-volume copy/delete fallback. Restore uses the same operation with the reviewed source and destination reversed. Purge uses ordinary FILE_DISPOSITION_INFO on the checked file handle, then verifies that the exact name is absent. Pending deletion or a failed postcondition is unresolved, not success. A purge is permanent and requires its separate approval and recovery acknowledgement through the coordinator.
The worker does not retain selected-root or selected-file handles between logical operations. It retains only its separate owned process/pipe/job lifetime and admitted source/private-namespace pins. ScopedRoot binds the selected root's identity across calls; it is not a persistent OS lock. Snapshot traversal includes final membership and metadata rechecks but is not a filesystem transaction. This service requires a quiet workspace controlled by its owner. It does not protect against an adversarial same-user process, privileged concurrent mutation, power loss or a hostile filesystem. After a timeout, interruption or any failure following possible mutation, reconcile the exact source and destination before considering another action; never automatically retry a consumed operation.
Owned serial worker lifecycle
One Python process owns a lazy serial generation keyed to its process identity, exact executable identity, fixed dispatcher/C# hashes and repository/private work roots. Closing a Python ScopedRoot closes that logical context, not the shared helper generation. Selected roots and expected records remain per-request inputs rather than generation authority. Warm requests reuse the interpreter and in-memory compiled type, not selected paths, file contents, results, ACL decisions or workflow authority. Each request runs in a fresh PowerShell function scope. Selected-file operations create a fresh C# Scope and use current root and expected file records with the existing NTFS, identity, sharing, hard-link/reparse, hash, no-overwrite and postcondition checks. Actual probe calls still go through the live generation; a cached ready state is not permission or proof about a selected volume.
There is one active request and at most one waiting caller; additional or same-thread reentrant dispatch refuses before delivery. Only inherited anonymous pipes connect parent and child. Frames have the fixed PCW1 magic, a little-endian length and strict UTF-8 JSON, with exact generation/sequence/request correlation. The request envelope is at most 8 KiB; existing packet/response bounds remain 100,000,000 encoded bytes and decoded read/create bounds remain 64 MiB. These byte bounds are not a peak-memory guarantee. There is no named endpoint, socket, daemon, reconnect address or action queue.
Native CreateProcessW associates the child with its unnamed owned job at creation, before its thread runs. Only the intended stdin/stdout ends and a non-sensitive stderr sink are inherited; the parent alone holds the non-inheritable kill-on-close job lifetime handle. Compiler descendants remain in that job. Normal retirement observes the exact job becoming empty, both I/O threads completing and all owned handles being disposed. A dead root process, cancelled I/O or a poison flag alone is insufficient. Abrupt parent death closes its job lifetime handle in the OS; termination still does not prove rollback of an effect.
Initial generation limits are 256 acknowledged requests, 60 seconds idle and 15 minutes age. Retirement occurs between acknowledged operations; an idle timer cannot take a reserved/in-flight generation. A replacement is withheld until the previous owned job and I/O teardown are positively complete. If completion cannot be proved within the teardown budget, TEARDOWN_UNRESOLVED retains the required handles/pins and blocks replacement. Source/runtime/private-root changes also require retirement rather than reusing a mismatched generation.
Caller serialization has a separate wait of up to 120 seconds. After admission, one monotonic 120-second exchange budget covers any cold startup/readiness and pipe write/reply; startup and operation do not get separate allowances. Failed-exchange teardown has a separately bounded five-second grace. Private preparation and final response persistence have payload bounds rather than a promised 120-second wall-clock return limit, so error handling and file I/O can extend total return time. A timeout, EOF, malformed/correlated-wrong response, worker death or persistence ambiguity after possible send never causes automatic resend, reconnect or one-shot fallback. Preserve the packet and consumed/unknown effect until actual receipts and postconditions reconcile it. A valid categorical native refusal remains distinct from a transport/protocol fault.
Call windows_scoped.shutdown_worker() explicitly when the trusted caller has finished adapter use and no operation is active. One five-second shutdown deadline covers call-lock waiting and retirement. True requires completed owned-job/I/O disposal at that point; False means shutdown was not proved, including a busy caller or unresolved teardown. Shutdown does not reserve a worker-free interval: a later native call can create a new generation. In a long-lived embedding process, require True before updating the selected PowerShell runtime or toolkit PS/C# sources. The coordinator must keep toolkit native calls quiet throughout that update or an external-installer interval; if it performs later native preflight, repeat the drain and require True before proceeding. See service integration for the Windows handoff's point-in-time drain. A busy/unproved shutdown is a hold, not permission to bypass sharing restrictions. It deletes no files, closes no user applications, searches no host PIDs and supplies no new approval. Normal CLI exit attempts the registered atexit shutdown, and the finite idle timer retires an unused generation. The process-owned job lifetime remains the abrupt-parent-death boundary. An explicit -RequestPath one-shot dispatcher mode remains available for separate direct invocations, never as the worker's error fallback.
Shared Python API and bounds
ScopedRoot(root, quiet_workspace=True, max_entries=20000) is a context manager with inspect, list_directory, snapshot, read_bytes, stream_to, mkdir, create_file, move_to and remove_file. It uses the shared scoped_fs.ScopedFSError and CapabilityHold exceptions. An exact absent leaf reports missing-entry; an inaccessible or missing ancestor is a different refusal. Relative paths use / and cannot escape the selected root. All content operations take an expected hashed file record.
Snapshots default to 20,000 records, a depth limit of 64, 64 MiB per hashed file and 256 MiB total hashed content. Metadata-only snapshots do not read file contents and do not impose the hash-byte limit. The Windows in-memory read, new-file creation and streaming implementation have a 64 MiB cap. stream_to buffers the complete checked read, then supplies bounded chunks to a trusted writer and refuses a short write. Larger streams produce an explicit capability hold. Native hashing and checked moves have a 64 GiB cap. A nonpositive or excessive bound is refused. The adapter does not silently spill to an unrelated location or weaken verification for a larger file.
capabilities() sends a real fixed probe through the selected worker generation without collecting a selected root. A cold probe starts the interpreter and compiles the fixed native source; a warm probe still makes a request rather than using a separate global positive-probe cache. The result reports operation symbols, the filesystem restriction and stream cap. Every actual operation still checks its selected filesystem, sharing, permissions and contents. A positive probe is not certification of every volume or permission state. Reuse is intended to avoid repeated interpreter/type startup; the focused native checks and resource observations describe the tested case, while broader correctness and timing claims require completed matching evidence.
Read-only diagnostic recipes
Dot-sourcing scripts/diagnostic_windows.ps1 defines functions and does not collect host evidence. Invoke-WindowsDiagnostic -Request <validated request> accepts only the shared patchcairn.diagnostics.v3 request and these fixed recipe IDs. A real collection also requires the explicit process-call switch -CoordinatorAuthorized; a request record by itself does not supply owner authority:
| Recipe | Bounded evidence | Meaning and limitation |
|---|---|---|
windows.os_version | OS version, build and a fixed architecture category | Context for support and compatibility research; does not establish patch completeness. |
windows.system_capacity | Total visible and currently available physical memory in bytes | A momentary capacity observation; does not identify the cause of a slowdown. |
windows.update_context | Up to 50 CBS hotfix KB identifiers and nullable installation dates | An incomplete update context. It does not establish complete history, a pending restart or security remediation. |
windows.system_event_metadata | At most 50 recent selected System events from a fixed provider and event-ID allowlist | Timestamp, numeric ID, numeric level and a public provider name only. |
windows.update_event_metadata | At most 50 recent warning/error/critical events in the WindowsUpdateClient Operational channel | The same metadata projection; no message or event payload is accessed. |
The memory and version recipes request specific Win32_OperatingSystem properties. The update recipe projects only KB identifiers and installation dates from Get-HotFix; computer and installer-account fields are not accessed or returned. Event recipes require explicit window_hours:1 through window_hours:24 and max_events:1 through max_events:50 scope tokens. The System provider allowlist is Kernel-Power, EventLog, WHEA-Logger, disk, Ntfs and volmgr. The event query also has a fixed ID set. Security/authentication logs, account identifiers, command lines, event messages, event XML and payload properties are excluded.
Results contain typed allowlisted facts, a UTC-Z observation, limitations and official provenance URLs. Unknown fields, executable-looking recipe IDs, broader scope and extra fixture fields are refused. Errors yield categorical holds; raw native error output is not copied into evidence. Fixture mode accepts only injected fictional facts and never invokes a host collector. A separate core API accepts an explicitly selected nonsecret excerpt; this adapter has no arbitrary log-file or command-reading API.
The recipes do not run DISM, SFC, CHKDSK repairs, Windows Update scans, resets or servicing commands. Those actions require a concrete separately authorized plan, an account of system-side logs and changes, and their own result verification. No permission barrier is bypassed when a selected event channel or CIM capability is inaccessible.
Evidence and test limits
tests/Test-WindowsScoped.ps1 creates fictional native files only below work/upgrade_v3/windows/. It checks content and identity binding, stale and same-size content refusal, no-overwrite quarantine/restore, exact purge, exclusive creation, hard-link and reparse refusal, root replacement, sharing barriers and fixed diagnostic validation. It runs no actual diagnostic recipe on the host. A separate owned Python bridge fixture exercises delegation through the fixed dispatcher. Test records state their actual PowerShell/Python runtime and scope; a fixture pass does not claim a maintenance run occurred.
Local Windows Python 3.14 fixture discovery completed 523 cases: 471 passed, 52 skipped, no failures or errors. Source-integrity checks confirmed the tested source was unchanged. The discovered total includes pure and mocked tests as well as native integration; skipped cases do not establish native success.
Focused native Windows NTFS fixtures created, inspected and read an exact 64-MiB fictional file under 64-bit PowerShell 7 (Core) and Windows PowerShell 5.1 (Desktop). Both editions validated the returned bytes and hashes, used one owned worker generation, and completed shutdown with the manager retired and all owned job/I/O resources quiescent. The 64-MiB payload cap and 120-second exchange budget remain unchanged.
A controlled Windows comparison used four balanced before/after pairs, each checking the same five fictional 16-KiB files through 20 native calls in a fresh Python process. Median API wall time was 17.19 seconds before reuse and 1.63 seconds with the owned worker, a 10.52-fold ratio for this workload; PowerShell starts fell from 20 to one per sample (95% fewer). The comparison used Python 3.14 and PowerShell 7.6.5, preserved file/hash checks and independently verified teardown, and performed no native file mutations. This is a local small-file workload result, not a whole-computer or Linux/macOS speed claim; OS caches and antivirus state were uncontrolled.
Observed worker process lifetime peaks were 2,515.53 MiB for Core and 1,471.54 MiB for Desktop. These are lifetime observations for the worker process, not per-operation peaks, total Python-plus-worker memory or a guaranteed memory ceiling. A payload byte cap does not bound the memory used by JSON/Base64 representations and runtime buffers. Neither suite completion nor these focused checks establish general workload speed, other Windows releases/filesystems, cross-volume operations, power-loss recovery or adversarial-concurrency guarantees. Match suite counts, timing comparisons and hosted results to their actual completed source-bound evidence; see platform evidence.
tests/test_private_workspace.py checks actual protected Windows directory/file owner/DACL admission and refusal of newly created fictional broad namespaces. It also verifies that a failed namespace admission writes no dispatcher payload and starts no subprocess, and that both content-bearing request and response files pass private-file admission in a legitimate fixture dispatch. These tests query only the current process token and owned fictional work objects; they do not inspect another account or change permissions on existing host folders.
Microsoft API and recipe provenance
These implementation choices follow the documented handle, sharing and metadata interfaces rather than evaluating commands supplied by evidence:
- CreateFileW documents sharing rules, directory handles and
FILE_FLAG_OPEN_REPARSE_POINT. - GetFinalPathNameByHandleW documents canonical handle paths.
- GetFileInformationByHandle documents native identity metadata and filesystem limits.
- SetFileInformationByHandle and FILE_RENAME_INFO document checked-handle rename, replacement control and disposition permissions.
- Win32_OperatingSystem documents the selected version and physical-memory properties.
- Get-HotFix documents the QuickFixEngineering/CBS update-history scope.
- Get-WinEvent documents
FilterHashtable, time bounds, levels, providers, IDs,MaxEventsand permission limits. - CreateDirectoryW, GetSecurityInfo, GetTokenInformation and ACE inheritance document the private namespace's creation-time security descriptor, actual owner/DACL readback, token identity and child protection.
Developed by Michael. Private session records belong locally under work/; this public guide contains no live computer records.
