Skip to content

[WSLC] State-aware sandbox lifecycle over the experimental.wslc surface (PR 2b/3) - #801

Open
Soham Das (SohamDas2021) wants to merge 5 commits into
mainfrom
user/sodas/wslc-state-aware-2b
Open

[WSLC] State-aware sandbox lifecycle over the experimental.wslc surface (PR 2b/3)#801
Soham Das (SohamDas2021) wants to merge 5 commits into
mainfrom
user/sodas/wslc-state-aware-2b

Conversation

@SohamDas2021

@SohamDas2021 Soham Das (SohamDas2021) commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

PR 2a/3 (#767) hardened the per-user WSLc daemon internals. This PR (2b/3) puts the
daemon to work: it wires the state-aware sandbox lifecycle (provision → start →
exec → stop → deprovision) through the public experimental.wslc.* wire schema, so a
caller can drive a long-lived WSLc container across multiple invocations instead of
one-shot run-to-completion.

No new public SDK types yet — that's PR 3/3. This PR lands the Rust backend, wire/schema
surface, engine dispatch, and the E2E harness.

Changes:

  • State-aware backendwslc/common/state_aware.rs implements StatefulSandboxBackend
    (dispatch prefix wslc), translating each lifecycle phase into daemon protocol frames
    over the owner-only named pipe via daemon_client. policy.rs maps + validates the
    per-phase policy (filesystem/network/host rules honored at provision; rejected where the
    backend can't enforce them).
  • Wire + schema — per-phase Wslc*Phase config added to the wire model
    (wxc_common::wire); regenerated dev schema (schemas/dev/mxc-config.schema.0.8.0-dev.json)
    and generated TS wire types (sdk/node/src/generated/wire.ts) — both codegen artifacts,
    not hand-edited.
  • Engine + dispatch wiringmxc_engine state-aware arm for WSLc, and the
    wslc sandbox-id prefix now resolves to ContainmentBackend::Wslc in
    state_aware_dispatch (with a unit test).
  • Daemon request handling — daemon main.rs handles the state-aware phase requests.
  • Fixtures + E2E — 18 tests/configs/wslc_state_aware_*.json fixtures (provision/start/
    exec/stop/deprovision plus rejection cases for denied paths, hosts, proxy, and filesystem)
    and a multi-invocation tests/scripts/run_wslc_state_aware_tests.ps1 harness with
    warm-reuse and idle-teardown assertions.
  • Docsdocs/wsl/wslc-state-aware.md (fixture ordering + sandbox-id substitution)
    and a copilot-instructions.md update.

Testing

  • cargo clippy --workspace --all-targets -- -D warnings and cargo fmt --check clean.
  • Daemon + wslc_common unit tests green (incl. the wslc-prefix dispatch resolution test);
    the G8 notify_one idle-wakeup regression test and the daemon resolve_duration env-override
    tests both pass.
  • build.bat --with-wslc succeeds.
  • WSLc one-shot corpus (run_wslc_all_tests.ps1) passing; the state-aware harness
    (run_wslc_state_aware_tests.ps1) drives provision → start → exec → exec → stop → deprovision
    on a WSL2 host.

Coming in the pipeline

Let me clean up the temp file I created.

🔗 References

✅ Checklist

📋 Issue Type

  • Bug fix
  • Feature
  • Task

GitHub Actions runs the PR validation build automatically. The ADO pipeline
(MXC-PR-Build) is the Azure version of the PR pipeline, kept in parity with the GitHub
Actions build; it runs on merge to main, and Microsoft reviewers with write access can trigger it
on a PR with /azp run. See docs/pull-requests.md.

If the dependency-feed-check check fails on a new dependency, the crate must be added to
the feed before the PR can pass. See docs/pull-requests.md
for the steps.

Microsoft Reviewers: Open in CodeFlow

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

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.

Pull request overview

Adds daemon-backed, state-aware WSLc lifecycle support across Rust dispatch, wire schema, documentation, and E2E coverage.

Changes:

  • Implements WSLc provision/start/exec/stop/deprovision dispatch and policy validation.
  • Extends generated schema and wire types with WSLc provision configuration.
  • Adds lifecycle fixtures, E2E harness, daemon timeout controls, and documentation.

Reviewed changes

Copilot reviewed 30 out of 31 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
.github/copilot-instructions.md Documents WSLc architecture and tests.
docs/wsl/wslc-state-aware.md Describes lifecycle behavior and limitations.
schemas/dev/mxc-config.schema.0.8.0-dev.json Adds generated WSLc provision schema.
sdk/node/src/generated/wire.ts Adds generated provision wire types.
src/backends/wslc/common/src/daemon_client.rs Adds typed errors and daemon spawning changes.
src/backends/wslc/common/src/lib.rs Exports state-aware modules.
src/backends/wslc/common/src/policy.rs Implements per-phase policy validation.
src/backends/wslc/common/src/state_aware.rs Implements the state-aware backend.
src/backends/wslc/daemon/src/main.rs Adds configurable idle timing.
src/core/mxc_engine/src/state_aware.rs Wires WSLc into engine dispatch.
src/core/wxc_common/src/state_aware_dispatch.rs Registers the wslc ID prefix.
src/core/wxc_common/src/wire.rs Adds WSLc provision wire configuration.
tests/configs/wslc_state_aware_stop.json Adds stop fixture.
tests/configs/wslc_state_aware_start.json Adds start fixture.
tests/configs/wslc_state_aware_provision.json Adds basic provision fixture.
tests/configs/wslc_state_aware_provision_with_filesystem.json Adds volume fixture.
tests/configs/wslc_state_aware_provision_rejected_proxy.json Tests provision-time proxy rejection.
tests/configs/wslc_state_aware_provision_rejected_hosts.json Tests host-filter rejection.
tests/configs/wslc_state_aware_provision_rejected_denied.json Tests denied-path rejection.
tests/configs/wslc_state_aware_provision_bridged.json Adds bridged-network fixture.
tests/configs/wslc_state_aware_exec_write_marker.json Writes warm-reuse marker.
tests/configs/wslc_state_aware_exec_rejected_filesystem.json Tests immutable filesystem policy.
tests/configs/wslc_state_aware_exec_read_marker.json Reads warm-reuse marker.
tests/configs/wslc_state_aware_exec_proxy.json Tests proxy environment injection.
tests/configs/wslc_state_aware_exec_exit_7.json Tests exit code 7.
tests/configs/wslc_state_aware_exec_exit_1.json Tests exit code 1.
tests/configs/wslc_state_aware_exec_exit_0.json Tests successful exit.
tests/configs/wslc_state_aware_exec_env.json Tests per-exec environment.
tests/configs/wslc_state_aware_exec_basic.json Adds basic exec fixture.
tests/configs/wslc_state_aware_deprovision.json Adds deprovision fixture.
tests/scripts/run_wslc_state_aware_tests.ps1 Adds multi-invocation E2E harness.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/backends/wslc/common/src/state_aware.rs
Comment thread tests/scripts/run_wslc_state_aware_tests.ps1
Comment thread src/core/mxc_engine/src/state_aware.rs
Comment thread src/backends/wslc/common/src/state_aware.rs Outdated
Comment thread docs/wsl/wslc-state-aware.md Outdated
Comment thread tests/scripts/run_wslc_state_aware_tests.ps1
Comment thread src/core/wxc_common/src/wire.rs Outdated
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

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.

Pull request overview

Copilot reviewed 31 out of 32 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

src/backends/wslc/common/src/policy.rs:124

  • This value-based check silently accepts an explicitly supplied network.defaultPolicy: "block" on post-provision phases, and it also misses explicit default-valued fields such as allowLocalNetwork: false or enforcementMode: "capabilities". Those settings are not applied after provision, despite the documented immutable-policy contract. Start/stop/deprovision can reject network_specified; exec needs field-level presence tracking so a proxy-only block remains allowed while all other network fields are rejected.
fn reject_non_default_network(request: &ExecutionRequest) -> Result<(), MxcError> {
    if request.policy.default_network_policy != NetworkPolicy::Block {
        return Err(MxcError::policy_validation(ERR_NETWORK_IMMUTABLE));
    }

src/backends/wslc/common/src/state_aware.rs:270

  • A missing WSLC runtime is reported by the daemon as WorkerError::Backend/ErrKind::Backend, which this maps to backend_error. This contradicts the documented backend_unavailable contract and means the E2E availability probe only skips feature-off builds; a host without the runtime continues into the suite and fails every lifecycle test. Add a distinct unavailable/not-ready classification for SDK load/prerequisite failures and map it to backend_unavailable.
        DaemonError::Daemon { kind, message } => match kind {
            ErrKind::NotProvisioned => MxcError::not_provisioned(message),
            ErrKind::NotStarted => MxcError::not_started(message),
            ErrKind::Busy | ErrKind::NotReady | ErrKind::Protocol | ErrKind::Backend => {
                MxcError::backend_error(message)
            }
        },
        DaemonError::Transport(e) => MxcError::backend_error(format!("{e:#}")),

tests/scripts/run_wslc_all_tests.ps1:342

  • The delegated harness exits 0 both when it passes and when it skips because the daemon/runtime is unavailable, but this always records Skipped = $false and counts either case as a pass. As a result, the advertised single entry point can report all coverage passing without running any state-aware tests. Preserve a distinct skip result from the child harness and record it here.

Comment thread src/core/mxc_engine/src/state_aware.rs
Copilot AI review requested due to automatic review settings August 10, 2026 20:17
@SohamDas2021
Soham Das (SohamDas2021) force-pushed the user/sodas/wslc-state-aware-2b branch from 499f351 to f96a091 Compare August 10, 2026 20:18

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.

Pull request overview

Copilot reviewed 33 out of 34 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

src/backends/wslc/common/src/policy.rs:123

  • This value-only check cannot distinguish an absent network section from an explicit defaultPolicy: "block". A sandbox provisioned as allow/Bridged therefore accepts a later exec requesting block but stays bridged, silently violating the requested restriction. Preserve presence for the network-mode fields (separately from a proxy-only network section) and reject any post-provision mode request.
fn reject_non_default_network(request: &ExecutionRequest) -> Result<(), MxcError> {
    if request.policy.default_network_policy != NetworkPolicy::Block {
        return Err(MxcError::policy_validation(ERR_NETWORK_IMMUTABLE));

src/backends/wslc/common/src/policy.rs:57

  • Provision validation accepts network.allowLocalNetwork=true, but map_network consumes only default_network_policy; no inbound-access or port-forward setting is applied. The one-shot WSLc path explicitly rejects this unsupported request (wsl_container_runner.rs:646-650), so state-aware provision should also fail rather than silently claiming the policy is honored.

This issue also appears on line 121 of the same file.

    reject_host_filtering(request)?;

src/backends/wslc/common/src/state_aware.rs:268

  • Missing WSLC runtime components fail inside load_sdk_checked/session creation as daemon ErrKind::Backend, so this maps them to backend_error. That contradicts the documented backend_unavailable contract and prevents the new E2E availability probe (which only skips backend_unavailable) from skipping unsupported hosts. Add a distinct daemon classification for runtime unavailability and map it accordingly.
            ErrKind::Busy | ErrKind::NotReady | ErrKind::Protocol | ErrKind::Backend => {
                MxcError::backend_error(message)
            }

src/core/wxc_common/src/wire.rs:527

  • This adds a configuration field to the generated schema, but the canonical schema reference still documents only the flat one-shot experimental.wslc shape. The repository convention requires schema additions to update docs/schema.md; add the nested state-aware provision shape there so callers can discover image and imageTarPath.
    /// State-aware provision-phase configuration
    /// (`experimental.wslc.provision`). Carries the container-creation knobs
    /// for the state-aware lifecycle; the flat sibling fields above remain the
    /// one-shot surface. Absent on one-shot configs and non-provision phases.
    pub provision: Option<WslcProvisionPhase>,

Comment thread src/backends/wslc/common/src/state_aware.rs
Copilot AI review requested due to automatic review settings August 10, 2026 20:30
@SohamDas2021
Soham Das (SohamDas2021) force-pushed the user/sodas/wslc-state-aware-2b branch from f96a091 to f311b6c Compare August 10, 2026 20:30

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.

Pull request overview

Copilot reviewed 33 out of 34 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

src/backends/wslc/common/src/state_aware.rs:73

  • State-aware provisioning maps the filesystem policy directly to mounts, bypassing the object-alias normalization and delegation checks used by the one-shot WSLc path (wsl_container_runner.rs:1282-1309). As a result, the same host object can be exposed read-write through one alias despite being read-only through another, and paths are mounted without the established caller-access check. Normalize/tighten the policy and run check_delegation before overlap validation and volume construction.
        let image_tar_path = config.and_then(|c| c.image_tar_path);
        let volumes = build_daemon_volumes(request)?;

tests/scripts/run_wslc_all_tests.ps1:342

  • The delegated script exits 0 for prerequisite skips (for example, a missing daemon or backend_unavailable), so this records a skipped state-aware suite as Pass = true, Skipped = false. The aggregate summary then claims the lifecycle coverage passed when none ran. Propagate a distinct skip result or perform the prerequisite probe here and set Skipped accurately.
    src/backends/wslc/common/src/policy.rs:58
  • The state-aware validator never rejects network.allowLocalNetwork=true, although map_network ignores it and the one-shot WSLc validator explicitly rejects it (wsl_container_runner.rs:646-650). Provision parsing catches this in the CLI path, but post-provision requests resolve by sandbox ID and direct engine callers bypass that parser guard, so the unsupported grant can be silently accepted. Apply the same rejection in the backend validation for every phase.
    reject_host_filtering(request)?;
    if request.policy.network_proxy.is_enabled() {

src/core/wxc_common/src/wire.rs:527

  • This adds a public config field, but the canonical schema reference in docs/schema.md still shows only the flat one-shot experimental.wslc fields and omits the nested provision shape. Update that reference so state-aware users can discover and author this field consistently with the generated schema.
    /// State-aware provision-phase configuration
    /// (`experimental.wslc.provision`). Carries the container-creation knobs
    /// for the state-aware lifecycle; the flat sibling fields above remain the
    /// one-shot surface. Absent on one-shot configs and non-provision phases.
    pub provision: Option<WslcProvisionPhase>,

Comment thread src/backends/wslc/common/src/policy.rs Outdated
Copilot AI review requested due to automatic review settings August 10, 2026 21:37
@SohamDas2021
Soham Das (SohamDas2021) force-pushed the user/sodas/wslc-state-aware-2b branch from f311b6c to f7802dc Compare August 10, 2026 21:37

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.

Pull request overview

Copilot reviewed 33 out of 34 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/backends/wslc/common/src/policy.rs:124

  • Comparing only the mapped value cannot distinguish an omitted network mode from an explicit defaultPolicy: "block". For example, after provisioning a bridged (allow) sandbox, an exec request that explicitly asks for block passes this check even though the container remains bridged, silently dropping a restrictive policy. Track presence of the mode field separately (while still allowing a proxy-only network block) and reject any post-provision mode request.
fn reject_non_default_network(request: &ExecutionRequest) -> Result<(), MxcError> {
    if request.policy.default_network_policy != NetworkPolicy::Block {
        return Err(MxcError::policy_validation(ERR_NETWORK_IMMUTABLE));
    }

tests/scripts/run_wslc_state_aware_tests.ps1:176

  • ProcessStartInfo.ArgumentList is unavailable in Windows PowerShell 5.1's .NET Framework, and this Windows harness does not require PowerShell 7. The first invocation therefore throws before any lifecycle test runs on the default Windows shell. These arguments contain no spaces, so assigning the joined string keeps the harness compatible with both editions.
    tests/scripts/run_wslc_all_tests.ps1:341
  • The child harness exits 0 both when it passes and when it skips because the daemon or backend is unavailable. Treating every zero as Pass = true and hard-coding Skipped = false makes the aggregate summary claim state-aware coverage passed when no state-aware test ran. Propagate a distinct skip status from the child (or otherwise report its prerequisite outcome) before adding this result.
    src/backends/wslc/common/src/state_aware.rs:268
  • A missing WSLC runtime is reported by the daemon worker as ErrKind::Backend because ensure_session maps load_sdk_checked failures to WorkerError::Backend; this arm therefore returns backend_error, not the documented backend_unavailable. As a result the new prerequisite probe does not skip a host without the runtime and the public error mapping contradicts this PR's documentation. Add a distinct unavailable classification through the worker/protocol/client mapping.
        DaemonError::Daemon { kind, message } => match kind {
            ErrKind::NotProvisioned => MxcError::not_provisioned(message),
            ErrKind::NotStarted => MxcError::not_started(message),
            ErrKind::Busy | ErrKind::NotReady | ErrKind::Protocol | ErrKind::Backend => {
                MxcError::backend_error(message)
            }

Base automatically changed from user/sodas/wslc-daemon-hardening to main August 10, 2026 23:01
…lc surface

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: f3ae1af2-7b79-4340-a5ce-a5402e7ede3d
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: f3ae1af2-7b79-4340-a5ce-a5402e7ede3d
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: f3ae1af2-7b79-4340-a5ce-a5402e7ede3d
@SohamDas2021
Soham Das (SohamDas2021) force-pushed the user/sodas/wslc-state-aware-2b branch from f7802dc to 20b01fc Compare August 10, 2026 23:01
Copilot AI review requested due to automatic review settings August 11, 2026 17:44

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.

Pull request overview

Copilot reviewed 33 out of 34 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/backends/wslc/common/src/state_aware.rs:73

  • Provision builds daemon mounts directly from the raw policy, bypassing the object-identity normalization and delegation check used by the one-shot WSLc path (wsl_container_runner.rs:1282-1325). A caller can therefore mount an alias that should have been tightened to denied, or delegate a path the caller is not authorized to share. Normalize first, run check_delegation on the effective policy, and re-run overlap validation before constructing VolumeMounts; PR #806’s follow-up diff confirms this gap and the required ordering.
        let volumes = build_daemon_volumes(request)?;

src/backends/wslc/common/src/policy.rs:126

  • This post-provision network check misses allowLocalNetwork. For start/exec/stop/deprovision, containment is resolved from sandboxId, so the parser cannot apply its WSLc-specific rejection; allowLocalNetwork=true therefore reaches this validator, passes when defaultPolicy is Block, and is silently ignored. Reject it here as the one-shot WSLc validator does at wsl_container_runner.rs:646-650.
fn reject_non_default_network(request: &ExecutionRequest) -> Result<(), MxcError> {
    if request.policy.default_network_policy != NetworkPolicy::Block {
        return Err(MxcError::policy_validation(ERR_NETWORK_IMMUTABLE));
    }
    Ok(())

docs/wsl/wslc-state-aware.md:59

  • The state-aware exec path does not forward stdin: it sends no stdin data and returns null_pipe_handle() for stdin in state_aware.rs:195. The linked PR #806 also explicitly defers piped stdin to a later tier, so documenting it as supported will mislead callers.
| exec | `WslcCreateContainerProcess` in the warm container; stream stdout/stderr, forward stdin, return the process exit code. A timeout SIGKILLs the **process**, not the container. |

tests/scripts/run_wslc_all_tests.ps1:342

  • The delegated harness exits 0 both when it passes and when it skips due to a missing daemon/backend, but this result is always recorded as Skipped = $false. Consequently the aggregate summary counts an unexecuted state-aware suite as a passing test. Return a distinct skip status/result from the child and map it to Skipped = $true here.
    tests/scripts/run_wslc_state_aware_tests.ps1:314
  • This probe does not actually catch a missing WSLc runtime as documented. The daemon becomes ready before loading the SDK; the first provision’s load_sdk_checked failure is converted to WorkerError::Backend, then ErrKind::Backend, and finally backend_error, so this backend_unavailable-only branch is skipped and the entire suite fails instead of skipping. Preserve an unavailable classification across the daemon protocol (without treating every backend error as a skip).

…network-mode changes by presence post-provision
Copilot AI review requested due to automatic review settings August 11, 2026 23:10

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.

Pull request overview

Copilot reviewed 35 out of 36 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/backends/wslc/common/src/state_aware.rs:94

  • Re-run denied-path overlap validation after object normalization. Normalization can move a mounted alias into deniedPaths while leaving its parent mount in place; because the only overlap check ran on the raw policy, the parent is then mounted and exposes the newly denied alias. The one-shot path deliberately validates again after normalization (wsl_container_runner.rs:1312-1327).
        let volumes = build_daemon_volumes(request)?;

src/backends/wslc/common/src/policy.rs:57

  • Provision currently accepts network.allowLocalNetwork=true, but map_network only maps defaultPolicy, so this requested network posture is silently ignored. The one-shot WSLc validator rejects this unsupported setting (wsl_container_runner.rs:646-650); state-aware provision should do the same rather than creating a sandbox with different exposure than requested.
    reject_host_filtering(request)?;

tests/scripts/run_wslc_state_aware_tests.ps1:176

  • ProcessStartInfo.ArgumentList is unavailable in the .NET Framework used by the default Windows PowerShell 5.1, so this harness throws before launching its first phase unless run under PowerShell 7. The repository does not require PowerShell 7 and sibling harnesses use Arguments; these arguments are flags plus base64, so joining them is safe and keeps the test runnable from Windows PowerShell.
    src/core/mxc_engine/src/state_aware.rs:35
  • This branch also runs on non-Windows targets, where the message incorrectly claims the build lacks the wslc feature even when that feature is enabled. Report both supported conditions so Linux/macOS callers receive an accurate availability error.
        "the WSLc backend is not available in this build (compiled without the `wslc` feature)",

src/core/wxc_common/src/wire.rs:541

  • The wire format has no top-level policy section; mounts and network mode come from top-level filesystem and network. This incorrect wording is propagated into the generated schema and TypeScript file, while docs/schema.md still shows only the flat one-shot WSLc fields. Update the source wording, document the nested state-aware provision shape in the schema reference, and regenerate both artifacts.
/// Filesystem mounts and network mode derive from the top-level `policy`
/// section (readwrite / readonly paths, network), not from here. The

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