Repository navigation
[WSLC] Honor portMappings on the state-aware provision surface - #1369
Soham Das (SohamDas2021) merged 4 commits into
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The exported Rust enum change breaks source compatibility, while diagnostics and test setup remain inconsistent with the new capability.
Review effort: Balanced
Findings: 1
Open (4)
Adding a field breaks downstream construction of the public enum variant · New Update obsolete diagnostic claiming state-aware mappings are unsupported · New Loopback-only probe misses all-interface port mapping collisions · New Legacy provision type no longer mirrors the portMappings wire contract · New
What changed in this PR
Adds state-aware WSLC host-to-container port forwarding while preserving one-shot behavior.
Changes:
- Threads
portMappingsthrough contracts, validation, SDK models, daemon IPC, and container creation. - Adds Rust SDK support and functional WSLC lifecycle coverage.
- Regenerates development artifacts and updates documentation.
| File | Description |
|---|---|
tests/scripts/run_wslc_state_aware_tests.ps1 |
Adds functional port-forwarding lifecycle test. |
tests/configs/wslc_state_aware_provision_port_mappings.json |
Adds mapped-port provision fixture. |
tests/configs/wslc_state_aware_exec_port_bind.json |
Adds container listener fixture. |
src/core/wxc_common/src/wire.rs |
Updates WSLC provision wire documentation. |
src/core/wxc_common/src/validator.rs |
Shares port validation logic. |
src/core/wxc_common/src/state_aware_operation.rs |
Validates lifecycle mappings. |
src/core/wxc_common/src/state_aware_input.rs |
Invokes operation validation. |
src/core/wxc_common/src/sdk_input.rs |
Enforces mapping contract version. |
src/core/wxc_common/src/policy_identity.rs |
Includes mappings in policy identity. |
src/core/wxc_common/src/models.rs |
Extends runtime provision configuration. |
src/core/wxc_common/src/config_parser.rs |
Reuses shared mapping validation. |
src/core/wxc_common/src/config_contract_adapters/v1_0/state_aware.rs |
Initializes unsupported mappings as absent. |
src/core/wxc_common/src/config_contract_adapters/v0_9/state_aware.rs |
Initializes unsupported mappings as absent. |
src/core/wxc_common/src/config_contract_adapters/dev/state_aware.rs |
Converts development mapping contracts. |
src/core/wxc_common/src/config_contract_adapters/dev/state_aware_tests/provision.rs |
Tests development adapter behavior. |
src/core/mxc-sdk/tests/state_aware.rs |
Tests public SDK exposure and validation. |
src/core/mxc-sdk/src/lib.rs |
Re-exports PortMapping. |
src/core/mxc-sdk/README.md |
Documents the Rust SDK API. |
src/core/mxc_engine/src/state_aware.rs |
Tests typed/exact request parity. |
src/core/mxc_engine/src/state_aware_sdk.rs |
Adds typed mapping API and conversion. |
src/core/mxc_engine/src/lib.rs |
Re-exports the mapping type. |
src/core/mxc_config_contract/tests/v1_1_0_alpha/state_aware/provision/wslc.rs |
Tests mapping contract constraints. |
src/core/mxc_config_contract/src/dev/state_aware/provision/wslc.rs |
Adds mappings to the development contract. |
src/backends/wslc/daemon/tests/daemon_ipc.rs |
Updates daemon lifecycle fixture. |
src/backends/wslc/daemon/src/session_manager.rs |
Applies mappings during container creation. |
src/backends/wslc/common/src/state_aware.rs |
Forwards mappings into daemon IPC. |
src/backends/wslc/common/src/daemon_protocol.rs |
Extends IPC and increments protocol version. |
src/backends/wslc/common/src/container_steps.rs |
Applies mappings to container settings. |
sdk/node/src/generated/v1_1_0_alpha/wire.ts |
Regenerates development wire types. |
schemas/dev/mxc-config.schema.1.1.0-alpha.json |
Regenerates the development schema. |
docs/wsl/wslc-state-aware.md |
Documents lifecycle port forwarding. |
docs/wsl/wsl-container-getting-started.md |
Adds WSLC mapping guidance. |
docs/versioning.md |
Documents the required contract version. |
docs/state-aware-lifecycle/mxc-state-aware-sandbox-api.md |
Updates lifecycle wire documentation. |
docs/state-aware-lifecycle/mxc-state-aware-sandbox-api-overview.md |
Updates the API overview. |
docs/schema.md |
Documents the new schema field. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
e67256d to
4d4065e
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Typed protocol validation can silently convert unsupported mappings to TCP, and several API, issue-scope, and documentation inconsistencies remain.
Review effort: Balanced
Findings: 2
Open (10)
Reject non-TCP protocols before state-aware mapping conversion · New Adding a field breaks downstream construction of the public enum variant Resolve remaining host-list parity gap or keep #824 open · New Loopback-only probe misses all-interface port mapping collisions Update obsolete diagnostic claiming state-aware mappings are unsupported Correct comment about WSLC support in typed Node API · New Clarify typed Rust API lacks port-mapping support · New Align port-mapping builder claims with implementation and docs · New Add identity tests for omitted and changed port mappings · New Legacy provision type no longer mirrors the portMappings wire contract
| /// Local image tarball to import instead of pulling an image. | ||
| pub image_tar_path: Option<String>, | ||
|
|
||
| /// Host-to-container TCP forwards applied to the sandbox's own container. | ||
| pub port_mappings: Option<Vec<PortMapping>>, |
There was a problem hiding this comment.
Is port_mappings only for TCP forwards? If so this can be renamed.
There was a problem hiding this comment.
TCP-only today, yes. The WSLC runtime returns E_NOTIMPL for UDP. I'd keep the name: the field is shared with the one-shot surface and matches the portMappings JSON key in the published 1.0.0 contract.
Gudge (MGudgin)
left a comment
There was a problem hiding this comment.
Summary
Filed our 14 deduplicated adversarial-review findings against 4d4065e: 10 Medium and 4 Low, all nonblocking. Seven findings are added as replies to five existing unresolved threads; the other seven are inline below. This is a COMMENT review, not an approval or a request for changes, and it does not adjudicate other reviewers' findings.
The PR is behind the current main tip 7e1ca09; attribution uses its actual merge base 016f2d4. The captured local three-dot diff and gh pr diff 1369 match exactly after line-ending normalization (65,761 UTF-8 bytes). Every new inline anchor is an added RIGHT-side line.
Source-verified checks
- The new mappings reach the daemon DTO, are converted at
session_manager.rs:718-726, and are supplied to container settings at lines 748 andcontainer_steps.rs:770. The functional regression probes the host and reads a sentinel; it is not merely an in-container bind check. PROTOCOL_VERSIONis 7, anddaemon_client.rs:297-303refuses an incompatible running daemon. Failed creation returns before sandbox registration, and failed start setsstartedonly after success (session_manager.rs:743-766,791-795). We are not alleging a demonstrated port leak.- High-level typed Rust emits
1.0.0and no mappings; Node/.NET also own stable1.0.0. The low-level public SDK constructor legitimately retains its alpha-only presence gate and early validation. Published WSLC provision structs remain closed and exclude this development field; released schemas are not changed by this diff.
These are source/diff checks, not new runtime test results. WSLC host suites were not rerun during filing. In particular, the target SDK's behavior for None networking plus explicit mappings, and its null-address bind scope, remain unverified. No policy bypass, broken forwarding under None, wildcard exposure, or substantial performance stall is presented as a confirmed defect.
Existing threads extended
- Findings 2 and 10: high-level typed/raw availability and the obsolete SDK-version pin explanation, replying to
discussion_r4161768408. - Findings 5 and 11: the additional bind/rebind race and qualified bind-scope clarification, replying to
discussion_r4161602997. - Finding 6: policy-identity regression cases, replying to
discussion_r4161768542. - Finding 9: the false Node-arm comment, replying to
discussion_r4161768378. - Finding 13: the edited placeholder comment, replying to
discussion_r4161603019.
Findings with evidence outside the changed files
Finding 7 - Medium, newly_exposed_by_change. src/core/wxc_common/src/state_aware_binding_tests.rs:76-80,437-462 is byte-identical to merge base and observes only image/tarball. The PR's new adapter field exposes a gap in this unchanged recorder: its preservation assertions cannot observe mappings. The inline comment is on the actual new field at config_contract_adapters/dev/state_aware.rs:84, not on the untouched test file.
Finding 14 - Low, newly_exposed_by_change. The published WSLC provision suites under tests/v0_9_0_alpha/state_aware/provision/wslc.rs and tests/v1_0_0/state_aware/provision/wslc.rs are byte-identical and reject only a generic unknown key, not this newly introduced alpha-only field. The request is a field-specific version-boundary regression, not a claim that published parsing currently accepts mappings. The inline anchor is the new development-contract field.
Findings 3 and 11 - newly_exposed_by_change. The concrete SDK-bound testing structure and runtime-selected address behavior predate the PR, but now apply to the new nonempty state-aware mapping path. We request targeted conversion coverage and a scoped runtime/documentation check, not a redesign of the existing SDK wrapper.
Verified pre-existing - not charged as defects of this PR
src/core/wxc_common/src/state_aware_binding_tests.rsand both published-version WSLC provision test files: byte-identical base/head. Only their newly relevant mapping-coverage gaps are attributed above.src/backends/wslc/common/src/policy.rsandwslcsdk_sys.rs: byte-identical base/head. Existing network selectors/declarations do not by themselves establish how the SDK handles explicit forwards withNone.- The existing placeholder type and typed SDK's stable-contract ownership are not new implementation defects. The new contradictory prose is classified as
claim_mismatch, capped at Medium and nonblocking.
All 14 findings were kept; findings 7 and 14 were explicitly rescoped from provisional introduced_by_change to newly_exposed_by_change. No finding rests on the withdrawn claims that the public SDK gate is unreachable, SDK errors are silently discarded, or mapped ports demonstrably leak.
| .map(|p| wxc_common::models::PortMapping { | ||
| windows_port: p.windows_port, | ||
| container_port: p.container_port, | ||
| protocol: "tcp".to_string(), |
There was a problem hiding this comment.
Low (performance) - Optional maximum-size shared-worker check (finding 12).
Attribution: introduced_by_change - the new conversion creates an owned mapping vector and one protocol String per entry on the daemon's serialized worker.
For valid raw exact TCP requests, nonzero u16 ports and duplicate-host-port rejection bound the list at 65,535 mappings. Commands are handled serially at lines 1050-1054, but settings make one bulk SDK call, not N+1 I/O. There is no measurement showing a substantial stall, and this is not an unbounded-input or merge-blocking claim.
Fix: Optionally benchmark/stress the maximum-size conversion plus SDK application before introducing a lower product limit or additional abstractions. Avoid the per-entry reconstruction only if that measurement shows it matters; normal small lists do not justify speculative optimization.
4d4065e to
9ca6d9b
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Direct one-shot engine inputs still bypass mapping validation and can silently coerce unsupported protocols to TCP.
Review effort: Balanced
Findings: 2
Open (5)
Validate protocol and port mappings for direct run requests · New Record CQ setup failures instead of skipping ownership assertions · New Add published WSLC version 1.0.0 to compatibility list · New Keep ProvisionStateAwareRequest aligned with the 1.0.0 contract · New Document bridged all-allow posture requirement for mappings · New
Resolved since last review (10)
Reject non-TCP protocols before state-aware mapping conversion Adding a field breaks downstream construction of the public enum variant Resolve remaining host-list parity gap or keep #824 open Loopback-only probe misses all-interface port mapping collisions Update obsolete diagnostic claiming state-aware mappings are unsupported Add identity tests for omitted and changed port mappings Align port-mapping builder claims with implementation and docs Clarify typed Rust API lacks port-mapping support Correct comment about WSLC support in typed Node API Legacy provision type no longer mirrors the portMappings wire contract
| policy::reject_unsupported_enforcement_mode(request).map_err(as_wslc_rejection)?; | ||
| policy::reject_port_mappings_without_bridged_network( | ||
| request, | ||
| "wslc.portMappings", | ||
| !self.config.port_mappings.is_empty(), | ||
| ) | ||
| .map_err(as_wslc_rejection)?; |
| $cqReady = $false | ||
| if ($null -ne $script:cqSandboxId -and (Envelope-Arm (Parse-Envelope -Stdout $cqSandbox.Start.Stdout)) -eq 'result') { | ||
| $script:cqStarted = $true | ||
| $r = Invoke-StateAware -ConfigFile 'wslc_state_aware_exec_port_bind.json' -SandboxId $script:cqSandboxId | ||
| $cqReady = ($r.ExitCode -eq 0 -and $r.Stdout -match 'LISTENER_STARTED') | ||
| } |
| - WSLC uses published `0.9.0-alpha`, or development `1.1.0-alpha` for | ||
| `wslc.provision.portMappings`; Windows Sandbox uses development | ||
| `1.1.0-alpha`. |
| @@ -409,7 +409,7 @@ interface ProvisionStateAwareRequest { | |||
| provision?: { appId?: string }; | |||
| }; | |||
| wslc?: { | |||
| provision?: { image?: string; imageTarPath?: string }; | |||
| provision?: { image?: string; imageTarPath?: string; portMappings?: PortMapping[] }; | |||
| TCP only — the WSLC SDK runtime returns `E_NOTIMPL` for UDP, so a `"udp"` | ||
| protocol is rejected. Two entries claiming the same `windowsPort` are also | ||
| rejected. |
9ca6d9b to
38947a4
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Dry-run validation currently misses the bridged-network requirement, and parts of the new test and documentation coverage can misreport or demonstrate invalid behavior.
Review effort: Balanced
Findings: 3
Open (7)
Validate provision mappings before dry-run returns · New Record CQ setup failures instead of skipping ownership assertions Validate protocol and port mappings for direct run requests Add all-allow bridged network to state-aware example · New Document bridged all-allow posture requirement for mappings Keep ProvisionStateAwareRequest aligned with the 1.0.0 contract Add published WSLC version 1.0.0 to compatibility list
| crate::wslc_common::policy::reject_port_mappings_without_bridged_network( | ||
| request, | ||
| "wslc.provision.portMappings", | ||
| !port_mappings.is_empty(), | ||
| )?; |
| { | ||
| "version": "1.1.0-alpha", | ||
| "phase": "provision", | ||
| "containment": "wslc", | ||
| "wslc": { | ||
| "provision": { | ||
| "portMappings": [ | ||
| { "windowsPort": 8080, "containerPort": 80 } | ||
| ] | ||
| } | ||
| } | ||
| } |
38947a4 to
cc594a3
Compare
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
Ownership tests can silently skip, and documentation currently presents invalid or stable-incompatible request shapes.
Review effort: Balanced
Findings: 3
Open (7)
Validate provision mappings before dry-run returns Record CQ setup failures instead of skipping ownership assertions Validate protocol and port mappings for direct run requests Add all-allow bridged network to state-aware example Document bridged all-allow posture requirement for mappings Keep ProvisionStateAwareRequest aligned with the 1.0.0 contract Add published WSLC version 1.0.0 to compatibility list
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d32878ac-e7c0-4db7-bbc3-1d6696a3003e
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d32878ac-e7c0-4db7-bbc3-1d6696a3003e
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d32878ac-e7c0-4db7-bbc3-1d6696a3003e
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d32878ac-e7c0-4db7-bbc3-1d6696a3003e
Gudge (MGudgin)
left a comment
There was a problem hiding this comment.
Re-verified our filed findings against cc594a3. Thirteen of fourteen are addressed: the networking guard and examples, typed/raw SDK documentation, daemon conversion coverage, direct SDK gate tests, bounded address-in-use retry, policy identity and binding preservation, lifecycle ownership/collision/reuse coverage, placeholder wording, and published-contract rejection cases.
The sole remaining item is finding 12, an optional maximum-size shared-worker benchmark. It remains Low severity and is not a merge condition; no meaningful performance stall was demonstrated.
Local verification passed 18 distinct targeted Rust tests with the wslc feature and five deterministic cases executing the actual retry-helper source with mocked lifecycle I/O. Commands run from src:
cargo test --locked -p mxc-sdk --lib --features wslc port_mappings- 10 passed.cargo test --locked -p mxc-sdk --lib --features wslc mxc_common::sdk_input::tests- 5 passed (three overlap the previous filter).cargo test --locked -p mxc-sdk --lib --features wslc state_aware_provision_hash_preserves_exact_config_shape- 1 passed.cargo test --locked -p mxc-sdk --lib --features wslc wslc_provision_preserves_each_backend_observable_configuration- 1 passed.cargo test --locked -p mxc-sdk --bin wxc-wslc-daemon --features wslc port_mappings- 2 passed.cargo test --locked -p mxc-sdk --features wslc --test mxc_contract_v0_9_0_alpha --test mxc_contract_v1_0_0 rejects_the_development_only_port_mappings_field- 2 passed.
The source checks also confirm both execution surfaces share the isolated-network mapping guard, daemon conversion preserves port order and TCP, the binding recorder observes optional mappings, and policy identity distinguishes their presence and values. The current GitHub/local diff matched exactly after line-ending normalization. The PR is behind main; verification used its actual merge base ae9e3c0, not the main tip.
Live WSLC suites and bind-scope measurements were not independently rerun in this verification. I inspected the new lifecycle assertions; the 91/91 state-aware run and loopback/start-phase measurements are author-reported runtime evidence, not local test results. No new approval claim is based solely on thread-resolution state.
Previously filed documentation claim mismatches are now addressed, not blockers. The pre-existing policy/SDK wrapper structure and placeholder type are not being charged as new defects, and no new out-of-diff issue is being introduced by this approval. Approved on the verified fixes with the optional benchmark left as a nonblocking follow-up.
cc594a3 to
e2ab4f3
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Direct one-shot requests can bypass mapping validation, and ownership tests can silently skip their assertions.
Review effort: Balanced
Findings: 4
Open (8)
Validate request mappings before parser bypass · New Validate provision mappings before dry-run returns Record CQ setup failures instead of skipping ownership assertions Validate protocol and port mappings for direct run requests Add all-allow bridged network to state-aware example Document bridged all-allow posture requirement for mappings Keep ProvisionStateAwareRequest aligned with the 1.0.0 contract Add published WSLC version 1.0.0 to compatibility list
| policy_mapping::container_working_directory(&request.working_directory) | ||
| .map_err(|msg| WslcError::Rejected(msg).into_response())?; | ||
| policy::reject_ui_policy(request).map_err(as_wslc_rejection)?; | ||
| policy::reject_port_mappings_without_bridged_network( |



📖 Description
A WSLC container driven through the state-aware lifecycle could not expose a port to the host. The identical
portMappingsblock already worked on the one-shot surface, so the two surfaces disagreed about what a WSLC container could do.wslc.provision.portMappingsnow works on the state-aware surface. Port mappings are container-scoped (WslcSetContainerSettingsPortMappings). The daemon keeps one warm session (VM) but creates a separate container for eachprovision, so a forward belongs to the container that declared it — unlike the session-wide sizing knobs (cpuCount/memoryMb/gpu/storagePath), which stay one-shot-only. A mapped port is reachable from the host on127.0.0.1only, and the forward is installed when the container starts rather than at provision.The capability is reachable through the raw
1.1.0-alphapath only. The high-level typed APIs in all three SDKs target stable1.0.0, which does not declare the field, and released schemas are immutable. Promoting1.1.0-alphato a stable release is what would let Rust, Node and .NET expose it together — this is not a Rust-only feature.The daemon IPC
PROTOCOL_VERSIONmoves 6 → 7, so a stale daemon from an older install is rejected rather than silently dropping the field.Port mappings now require bridged networking
The WSLC runtime refuses a container that combines port mappings with isolated networking:
WslcCreateContainerfails with a rawHRESULT 0x80070057. Both surfaces now reject that combination during validation, before any container is created, with a message naming the field to remove.This changes behavior on the stable one-shot surface. A one-shot request pairing
wslc.portMappingswith an omitted or all-denynetworkblock was previously accepted and then failed at container creation. It is now rejected up front. The request never actually worked — only the diagnostic changed.The two surfaces share one rule and differ only in the field path they name (
wslc.portMappingsvswslc.provision.portMappings), so they cannot drift apart again.🔗 References
Resolves #824.
#824 originally reported a second gap — redundant host lists accepted and ignored on the one-shot surface. That gap is obsolete and has been struck from the issue: #1382 retired the pre-v0.9 contracts along with the legacy
allowedHosts/blockedHostsfields, and the current directional schema cannot express the shape.Supersedes #1042 (closed unmerged 2026-09-24); implemented fresh on
main.Rebased onto #1270 (published exact 1.0 and opened 1.1 development), #1271 (made the v1 SDKs own their contract version, moving Windows Sandbox state-aware to the raw exact path), #1382 (retired the pre-v0.9 contracts) and #1383 (normalized directional network input directly).
🔍 Validation
Run on a Windows 11 + WSL2 host with the WSLC SDK runtime.
wslc_port_mapping_tcpandwslc_port_mapping_multiple.cargo test --workspace: 102 suites, 0 failures. Also run with--features wslc(17 suites), which--workspacealone does not compile.cargo fmt --all -- --checkandcargo clippy --workspace --all-targets -- -D warningsclean.validate-configs.js,check-schema-versions.js,check-contract-codegen.js,check-dotnet-api-parity.jsall pass.Schemas and TypeScript wire types were regenerated with
mxc_schema_gen;schemas/stable/is untouched.✅ Checklist
Cargo.lock, thedependency-feed-checkcheck passes (see docs/pull-requests.md)📋 Issue Type
Microsoft Reviewers: Open in CodeFlow