refactor(backends): unify the library backends on SandboxBackend + route binaries via Runner#555
Merged
Merged
Conversation
…d / SandboxProcess / Runner)
Introduce the shared, no-pty execution surface that the containment backends
and a future in-process library build on. Purely additive: no existing code
path uses these yet, so behavior is unchanged.
- `SandboxProcess` — a handle to a running sandboxed child: `take_stdin` /
`take_stdout` / `take_stderr`, `try_wait`, `id`, `kill` (process-tree), and
`wait` (drains any untaken stdio, honors `scriptTimeout`), plus stdout/stderr
closers for abandoning a backgrounded-descendant-held read without a kill.
- `SandboxBackend` — `validate` + `spawn(request, logger, StdioMode) ->
SandboxProcess` + a `diagnose_exit` hook; `StdioMode::{Pipes, Inherit}`.
- `Runner<B>` — the generic adapter bridging any `SandboxBackend` to the
run-to-completion `ScriptRunner` (spawn `Inherit`, then `wait`).
- `StreamCloser`, `group_kill` (Unix leader-first SIGKILL of the child's group),
and `wait_with_timeout` (adaptive 1ms->50ms backoff poll).
- `interruptible_reader` (Unix self-pipe + `poll`) and the Windows pipe helpers
in `process_util` (`InterruptiblePipeReader` / `PipeReadCanceller` /
`create_std_pipes`) for out-of-band-cancelable streaming reads.
- `FailurePhase::Timeout` so a timeout is distinguishable from other failures.
The library backends and executor binaries are migrated onto this surface in a
follow-up PR, and the importable `mxc-sdk` crate is built on top of it.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
…ute binaries via Runner Migrate the three in-process backends — Seatbelt (macOS), Bubblewrap (Linux), and Windows ProcessContainer (AppContainer + BaseContainer) — onto the SandboxBackend / SandboxProcess interfaces added in the previous PR, and route the executor binaries (wxc-exec, lxc-exec, mxc-exec-mac) through the generic Runner<B> adapter. The old per-backend run-to-completion logic is removed; each backend now exposes only spawn(), and Runner provides the single ScriptRunner the binaries dispatch on (spawn StdioMode::Inherit, then wait). Each backend gains a streaming handle with whole-process-tree termination (Unix process-group SIGKILL; Windows job-object terminate) and a uniform io::ErrorKind::TimedOut on scriptTimeout. Intentional behavior changes for existing binaries (call-outs for review): - Seatbelt now always env_clear()s the child (previously only when process.env was non-empty), aligning the binary with the SDK's documented "host env is not inherited" contract. - Seatbelt resolves an empty process.cwd to a policy read-write path (or "/") instead of the launcher's cwd. - Seatbelt/Bubblewrap inherit the executor's own stdio (StdioMode::Inherit) — Seatbelt no longer allocates a private pty, and Bubblewrap no longer forces stdin to /dev/null or post-exit-captures stdout/stderr (it streams live). - BaseContainer now places the child in a UiJobObject for tree-kill (it had none before); the child is created suspended, assigned to the job, then resumed so no descendant can escape the kill window. - kill() is a no-op once the child has been reaped, so it never signals a recycled pid/process-group. The macOS Seatbelt characterization suite is updated to assert the new env/cwd/ streaming/timeout behavior; the LXC and Seatbelt backend docs are updated to match. The default LXC path keeps its native pty and is unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
7 tasks
Contributor
There was a problem hiding this comment.
Pull request overview
This PR (part 2/3 of the #524 split, stacked on #554) migrates the in-process Seatbelt (macOS), Bubblewrap (Linux), and Windows ProcessContainer (AppContainer/BaseContainer) backends to the new SandboxBackend / SandboxProcess interfaces and routes the executor binaries through the generic Runner<B> adapter in wxc_common.
Changes:
- Introduces handle-based sandbox execution (
SandboxProcess,SandboxBackend,Runner) and shared helpers for timeouts, stream draining, and process-tree termination. - Refactors Seatbelt, Bubblewrap, and Windows ProcessContainer backends to expose
spawn()only (streaming-capable) and removes old backend-specific run-to-completion logic from binaries. - Updates characterization tests and docs to reflect intentional Seatbelt behavior changes (env clearing, default cwd resolution, stdio inheritance) and adjusts Windows networking/job-object logic for correctness across threads and tree-kill.
Show a summary per file
| File | Description |
|---|---|
| src/testing/wxc_e2e_tests/tests/e2e_seatbelt_characterization.rs | Updates Seatbelt characterization assertions for env-clearing and default-cwd behavior under the unified runner model. |
| src/core/wxc/src/main.rs | Routes Windows AppContainer execution through Runner<B>. |
| src/core/wxc_common/src/sandbox_process.rs | Adds the new cross-backend SandboxProcess / SandboxBackend interfaces and Runner adapter plus shared helpers. |
| src/core/wxc_common/src/process_util.rs | Adds Windows pipe reader/writer and interruptible read-canceller primitives used by streaming backends. |
| src/core/wxc_common/src/models.rs | Adds FailurePhase::Timeout to distinguish timeouts from other failures. |
| src/core/wxc_common/src/lib.rs | Exposes the new sandbox_process module and unix interruptible_reader module. |
| src/core/wxc_common/src/interruptible_reader.rs | Adds Unix interruptible pipe reader + canceller used by Seatbelt/Bubblewrap streaming stdio. |
| src/core/wxc_common/Cargo.toml | Broadens libc dependency to all unix targets (not just Linux). |
| src/core/mxc_darwin/src/main.rs | Routes macOS Seatbelt execution through Runner<B>. |
| src/core/lxc/src/main.rs | Routes Bubblewrap execution through Runner<B> (Linux path). |
| src/Cargo.lock | Removes mxc_pty dependency from seatbelt_common (Seatbelt no longer allocates a private PTY). |
| src/backends/seatbelt/common/src/seatbelt_runner.rs | Refactors Seatbelt runner to implement SandboxBackend and return a streaming SandboxProcess handle. |
| src/backends/seatbelt/common/src/profile_builder.rs | Updates comments to reflect inherited-TTY behavior; exposes tilde expansion helper for cwd resolution. |
| src/backends/seatbelt/common/Cargo.toml | Drops mxc_pty dependency (Seatbelt no longer uses the PTY bridge). |
| src/backends/bubblewrap/common/src/bwrap_runner.rs | Refactors Bubblewrap to SandboxBackend/SandboxProcess, enabling live streaming and tree-kill semantics. |
| src/backends/appcontainer/common/src/probe.rs | Makes UiCapabilitySupport cloneable. |
| src/backends/appcontainer/common/src/network_manager.rs | Refactors firewall COM initialization to be per-call/apartment-self-contained (thread-safe teardown). |
| src/backends/appcontainer/common/src/job_object.rs | Adds UiJobObject::terminate for whole-process-tree termination. |
| src/backends/appcontainer/common/src/dispatcher.rs | Wraps BaseContainer/AppContainer runners in Runner<B> during dispatch. |
| src/backends/appcontainer/common/src/base_container_runner.rs | Refactors BaseContainer to SandboxBackend/SandboxProcess, adds job-object assignment and capture pipes for streaming. |
| src/backends/appcontainer/common/src/appcontainer_runner.rs | Refactors AppContainer to SandboxBackend/SandboxProcess, adds capture pipes for streaming and fixes COM/thread-affinity issues in teardown. |
| docs/macos-support/seatbelt-backend.md | Documents Seatbelt env-clearing and default working-directory behavior. |
| docs/lxc-support/lxc-backend.md | Updates LXC env behavior wording (no longer references Seatbelt’s old conditional env-clearing behavior). |
Copilot's findings
- Files reviewed: 22/23 changed files
- Comments generated: 3
The self-pipe used to wake `InterruptibleReader`'s `poll` was created with `libc::pipe`, which does not set close-on-exec. Both wake fds would then leak into any process the thread later forks+execs (e.g. another sandbox child) and keep the wake pipe alive unexpectedly. Mark both ends `FD_CLOEXEC` after `pipe()`, mirroring the fixup `mxc_pty` already does for PTY fds. The data pipe is unaffected -- Rust already sets CLOEXEC on `Child` stdio. Addresses a Copilot review comment on #555. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
`group_kill` discarded both `kill(2)` results and always returned `Ok(())`, so `SandboxProcess::kill()` reported success even when it never signalled the process group. Route both signals through a `send_sigkill` helper that returns the error instead, treating only the "already gone" outcomes as success: `ESRCH`, and `EPERM` -- which on macOS a redundant kill of an exited-but-unreaped child's group reports in place of `ESRCH` (observed via the double-kill test). The caller guards with `try_wait()` first, so the pid/pgid can't be recycled and `EPERM` here can only be that benign race, never a real permission failure. Addresses a Copilot review comment on #556. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
# Conflicts: # src/core/wxc_common/src/interruptible_reader.rs # src/core/wxc_common/src/sandbox_process.rs
MGudgin
approved these changes
Jun 24, 2026
MGudgin
pushed a commit
that referenced
this pull request
Jun 24, 2026
) * feat(wxc_common): add the sandbox execution interfaces (SandboxBackend / SandboxProcess / Runner) Introduce the shared, no-pty execution surface that the containment backends and a future in-process library build on. Purely additive: no existing code path uses these yet, so behavior is unchanged. - `SandboxProcess` — a handle to a running sandboxed child: `take_stdin` / `take_stdout` / `take_stderr`, `try_wait`, `id`, `kill` (process-tree), and `wait` (drains any untaken stdio, honors `scriptTimeout`), plus stdout/stderr closers for abandoning a backgrounded-descendant-held read without a kill. - `SandboxBackend` — `validate` + `spawn(request, logger, StdioMode) -> SandboxProcess` + a `diagnose_exit` hook; `StdioMode::{Pipes, Inherit}`. - `Runner<B>` — the generic adapter bridging any `SandboxBackend` to the run-to-completion `ScriptRunner` (spawn `Inherit`, then `wait`). - `StreamCloser`, `group_kill` (Unix leader-first SIGKILL of the child's group), and `wait_with_timeout` (adaptive 1ms->50ms backoff poll). - `interruptible_reader` (Unix self-pipe + `poll`) and the Windows pipe helpers in `process_util` (`InterruptiblePipeReader` / `PipeReadCanceller` / `create_std_pipes`) for out-of-band-cancelable streaming reads. - `FailurePhase::Timeout` so a timeout is distinguishable from other failures. The library backends and executor binaries are migrated onto this surface in a follow-up PR, and the importable `mxc-sdk` crate is built on top of it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com> * refactor(backends): unify the library backends on SandboxBackend + route binaries via Runner Migrate the three in-process backends — Seatbelt (macOS), Bubblewrap (Linux), and Windows ProcessContainer (AppContainer + BaseContainer) — onto the SandboxBackend / SandboxProcess interfaces added in the previous PR, and route the executor binaries (wxc-exec, lxc-exec, mxc-exec-mac) through the generic Runner<B> adapter. The old per-backend run-to-completion logic is removed; each backend now exposes only spawn(), and Runner provides the single ScriptRunner the binaries dispatch on (spawn StdioMode::Inherit, then wait). Each backend gains a streaming handle with whole-process-tree termination (Unix process-group SIGKILL; Windows job-object terminate) and a uniform io::ErrorKind::TimedOut on scriptTimeout. Intentional behavior changes for existing binaries (call-outs for review): - Seatbelt now always env_clear()s the child (previously only when process.env was non-empty), aligning the binary with the SDK's documented "host env is not inherited" contract. - Seatbelt resolves an empty process.cwd to a policy read-write path (or "/") instead of the launcher's cwd. - Seatbelt/Bubblewrap inherit the executor's own stdio (StdioMode::Inherit) — Seatbelt no longer allocates a private pty, and Bubblewrap no longer forces stdin to /dev/null or post-exit-captures stdout/stderr (it streams live). - BaseContainer now places the child in a UiJobObject for tree-kill (it had none before); the child is created suspended, assigned to the job, then resumed so no descendant can escape the kill window. - kill() is a no-op once the child has been reaped, so it never signals a recycled pid/process-group. The macOS Seatbelt characterization suite is updated to assert the new env/cwd/ streaming/timeout behavior; the LXC and Seatbelt backend docs are updated to match. The default LXC path keeps its native pty and is unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com> * feat(mxc-sdk): add the importable in-process sandbox library crate Add `mxc-sdk` (lib `mxc_sdk`), a Rust library for starting MXC sandboxes in-process without a pty, built on the SandboxBackend interfaces and the unified backends from the previous PRs. Callers stream a sandboxed process without shelling out to the executor binaries or depending on the TypeScript SDK. Public surface (crate-owned types — `wxc_common` stays an implementation detail): - `build_request(&SandboxPolicy)` -> `SandboxRequest`, then `spawn_sandbox` -> `Sandbox`: a handle for bidirectional stdio (`take_stdin`/`take_stdout`/ `take_stderr`), `try_wait`, `id`, `kill` (process-tree), and `wait` -> `WaitOutcome` (`Exited(i32)` | `TimedOut`), plus stdout/stderr closers. - `Error` / `ErrorCode` mirror the wire-format error one-for-one. - `mxc_sdk::policy` ports the SDK's config building (`createConfigFromPolicy` plus `available_tools_policy` / `user_profile_policy` / `temporary_files_policy`); `platform_support` ports `getPlatformSupport`. Backends: Bubblewrap (Linux), Seatbelt (macOS), Windows ProcessContainer (AppContainer + BaseContainer). Other backends and LXC return `UnsupportedContainment` (LXC has no non-pty capture path); `dry_run` is rejected for streaming spawns. Adds `wxc_common::config_parser::load_request_from_value` so the crate maps a config it already holds as JSON without a base64 round-trip. The in-crate backend dispatch (`dispatch.rs`) and host probe (`platform.rs`) are marked provisional — a follow-up moves them into a shared `mxc` engine crate that both this library and the executor binaries call into. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com> * feat(mxc-sdk): add Sandbox::wait_with_output to drain both streams safely Taking both take_stdout() and take_stderr() and reading them sequentially can deadlock an output-heavy child (one pipe fills while the reader is blocked on the other). Add wait_with_output(): it consumes the handle, drains stdout and stderr concurrently on separate threads, and returns Output { outcome, stdout, stderr } -- the safe, convenient default, mirroring std::process::Child::wait_with_output. Review CR-24. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com> * test(mxc-sdk): cover dispatch guard branches for unsupported containment/host The streaming dispatch in `dispatch.rs` only had direct tests for the `dry_run` and macOS `guiAccess` rejection branches. Add two more so the remaining guardrails are exercised in CI: - `streaming_rejects_unsupported_containment`: drives the internal model with `containment = Lxc` and asserts `UnsupportedContainment` plus the backend name in the message. - `host_support_ok_on_supported_platforms`: cfg-gated to Windows / Linux / macOS, guards against the `ensure_host_supported` cfg list dropping a supported platform. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com> * feat(mxc-sdk): take structured (key, value) pairs in SandboxRequest::set_env set_env(Vec<String>) made the caller hand-format raw KEY=VALUE strings, which diverged from the SDK's env channel -- injectEnvIntoConfig (sdk/src/sandbox.ts) takes a structured { key: value } map and joins it to the KEY=VALUE wire form internally. Accept (key, value) pairs instead and do the formatting in the setter, so the crate matches the SDK surface and callers can't forget the '='. The wire representation (Vec<String> of KEY=VALUE) is unchanged, and iteration order is preserved so a later duplicate key still wins downstream -- same as the SDK. No eager validation is added: the SDK doesn't validate either, and structured input already removes the malformed-entry foot-gun. Adds a unit test asserting the pair-to-KEY=VALUE ordered mapping. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com> * fix(wxc_common): set FD_CLOEXEC on the interruptible-reader wake pipe The self-pipe used to wake `InterruptibleReader`'s `poll` was created with `libc::pipe`, which does not set close-on-exec. Both wake fds would then leak into any process the thread later forks+execs (e.g. another sandbox child) and keep the wake pipe alive unexpectedly. Mark both ends `FD_CLOEXEC` after `pipe()`, mirroring the fixup `mxc_pty` already does for PTY fds. The data pipe is unaffected -- Rust already sets CLOEXEC on `Child` stdio. Addresses a Copilot review comment on #555. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com> * fix(wxc_common): propagate non-benign group_kill signal errors `group_kill` discarded both `kill(2)` results and always returned `Ok(())`, so `SandboxProcess::kill()` reported success even when it never signalled the process group. Route both signals through a `send_sigkill` helper that returns the error instead, treating only the "already gone" outcomes as success: `ESRCH`, and `EPERM` -- which on macOS a redundant kill of an exited-but-unreaped child's group reports in place of `ESRCH` (observed via the double-kill test). The caller guards with `try_wait()` first, so the pid/pgid can't be recycled and `EPERM` here can only be that benign race, never a real permission failure. Addresses a Copilot review comment on #556. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com> * test(mxc-sdk): move the Windows streaming test out of the macOS-gated file `tests/streaming.rs` is `#![cfg(target_os = "macos")]`, so the `#[cfg(target_os = "windows")]` ProcessContainer streaming test it contained could never compile -- the intended Windows coverage was silently missing. Move that test into its own `tests/streaming_processcontainer.rs`, gated `#![cfg(target_os = "windows")]`, so it actually builds on Windows. Addresses a Copilot review comment on #556. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com> --------- Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MGudgin
pushed a commit
that referenced
this pull request
Jun 26, 2026
This PR plumbs `deniedPaths` into the BaseContainer SandboxSpec `fs_deny` FlatBuffer field so the OS enforces denied paths natively at Tier 1, gated on the OS-advertised `SANDBOX_CAP_FS_DENY` capability. When the capability is absent, a denied-paths policy falls through to a DACL-enforcing AppContainer tier (T2/T3) rather than staying on Tier 1 (where the dispatcher applies no host DACL), so deny is never silently dropped. Details * BaseContainerSpecification.fbs / sandbox_spec_generated.rs: add the additive `fs_deny:[string]` field so deniedPaths reach Experimental_CreateProcessInSandbox. * base_container_runner.rs: emit `fs_deny` from `denied_paths`; `validate` accepts deniedPaths only when the OS advertises SANDBOX_CAP_FS_DENY (via the existing `base_container_supports_deny_paths()` query) and otherwise fails closed -- this runner has no DACL fallback of its own. * fallback_detector.rs: `detect` keeps a denied-paths policy on Tier 1 only when the capability is present (native fs_deny, no host DACL); otherwise it falls through to the AppContainer DACL tiers. Adds a process-cached, test-seamed `base_container_supports_deny_paths()` wrapper mirroring `is_base_container_usable()`. * Rename the capability bit SANDBOX_CAP_DENY_PATHS -> SANDBOX_CAP_FS_DENY to match the OS-side capability name; correct the dispatcher's stale "opaque principal" comments (the child runs under a derivable AppContainer SID -- the real caveat is that BaseContainerRunner may pass an ephemeral identity, so a host DACL SID derived from the container name may not match). * error.rs: add DENIED_PATHS_FEATURE_DISABLED_MSG for the capability-absent direct-caller rejection. Reconciles onto main's capability-query (#547) and SandboxBackend/Runner refactor (#555): drops the original commit's parallel native_fs_deny_supported machinery in favor of main's single `base_container_supports_deny_paths()` query. Tests * cargo test -p appcontainer_common -p wxc_common: 129 + 347 passed, including new fallback-detector tests (T1 native-deny when capability present; fall-through when absent) and runner validate tests (accept/reject by capability) using a new MXC_FORCE_DENY_PATHS test seam. * cargo fmt --all -- --check; cargo clippy -p appcontainer_common -p wxc_common --all-targets -- -D warnings; cargo check --workspace --all-targets: all clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Generated-with: claude-opus-4.8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📖 Description
Part 2 of 3 splitting #524. This PR migrates the three in-process backends — Seatbelt (macOS), Bubblewrap (Linux), and Windows ProcessContainer (AppContainer + BaseContainer) — onto the
SandboxBackend/SandboxProcessinterfaces from #554, and routes the executor binaries (wxc-exec,lxc-exec,mxc-exec-mac) through the genericRunner<B>adapter. The old per-backend run-to-completion logic is removed; each backend now exposes onlyspawn(), andRunnerprovides the singleScriptRunnerthe binaries dispatch on.Each backend gains a streaming handle with whole-process-tree termination (Unix process-group
SIGKILL; Windows job-object terminate) and a uniformio::ErrorKind::TimedOutonscriptTimeout.Intentional behavior changes to the existing binaries (please review):
env_clear()s the child (was: only whenprocess.envwas non-empty), aligning the binary with the SDK's documented "host env is not inherited" contract.process.cwdto a policy read-write path (or/) instead of the launcher's cwd.StdioMode::Inherit) — Seatbelt no longer allocates a private pty, and Bubblewrap no longer forces stdin to/dev/nullor post-exit-captures stdout/stderr (it streams live).UiJobObjectfor tree-kill (it had none); the child is created suspended, assigned to the job, then resumed so no descendant can escape the kill window.kill()is a no-op once the child has been reaped, so it never signals a recycled pid/process-group.The macOS Seatbelt characterization suite is updated to assert the new behavior; the LXC and Seatbelt backend docs are updated to match. The default LXC path keeps its native pty and is unaffected.
🔗 References
unify-backendscommit here. Part 3 (mxc-sdk) builds on this.🔍 Validation
seatbelt_common36 unit tests pass; the 7 Seatbelt e2e characterization tests pass (env-clear, cwd, exit code, streaming, timeout tree-kill).cargo clippy --all-targets -- -D warningsclean forappcontainer_common+wxc(Windows target) andbwrap_common(Linux target);mxc_darwinbuilds.cargo fmt --all -- --checkclean.✅ Checklist
📋 Issue Type
Microsoft Reviewers: Open in CodeFlow