Skip to content

Add backend support probe API design & discussion doc - #717

Merged
Huzaifa Danish (huzaifa-d) merged 5 commits into
mainfrom
user/modanish/backend-probe-api
Aug 7, 2026
Merged

Add backend support probe API design & discussion doc#717
Huzaifa Danish (huzaifa-d) merged 5 commits into
mainfrom
user/modanish/backend-probe-api

Conversation

@huzaifa-d

@huzaifa-d Huzaifa Danish (huzaifa-d) commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a design & discussion document for a proposed read-only Rust available_backends() host-capability probe API.

Changes:

  • Defines the proposed API and isolation-tier semantics.
  • Documents existing detection methods and gaps.
  • Outlines testing and follow-up work.
Details
File Description
docs/backend-support-probe-api-plan.md Adds the backend-support probe API design.

Review details

Microsoft Reviewers: Open in CodeFlow

Design-only doc for a read-only Rust available_backends() host-capability probe: API shape, isolation-tier ceiling model, current per-backend detection methods and their risks, the remaining probe gap, testing, and follow-up work.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings July 30, 2026 22:18
@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 a discussion document proposing a Rust API for probing available containment backends.

Changes:

  • Defines the proposed API and isolation-tier semantics.
  • Documents existing detection methods and gaps.
  • Outlines testing and follow-up work.
Show a summary per file
File Description
docs/backend-support-probe-api-plan.md Adds the backend-support probe API design.

Review details

Comments suppressed due to low confidence (3)

docs/backend-support-probe-api-plan.md:119

  • There is no IsoSessionApp.dll resolvability gate in the TypeScript platform probe to port; isIsoSessionSupported() only checks CurrentBuild == 26300 and UBR >= 8553 (sdk/node/src/platform.ts:96-105). The Rust work must add the DLL check rather than copy it from TypeScript.
2. `isolation_session` - port the build-number + `IsoSessionApp.dll` gate from TypeScript to Rust.

docs/backend-support-probe-api-plan.md:110

  • These PATH-based probes are synchronous and the plan specifies no deadline. A broken or wrapper-provided bwrap/lxc-ls can therefore block available_backends() indefinitely even though the API is intended for startup discovery. Require a bounded timeout and add a test that a hung probe is treated as unavailable.
- On Linux, `bubblewrap` and `lxc` each appear when their check passes (`bwrap --version` / `lxc-ls --version`).

docs/backend-support-probe-api-plan.md:141

  • Remove the duplicated space.
The named tier is  the strongest isolation the host is capable
  • Files reviewed: 1/1 changed files
  • Comments generated: 6
  • Review effort level: Balanced

Comment thread docs/backend-support-probe-api-plan.md
Comment thread docs/backend-support-probe-api-plan.md Outdated
Comment thread docs/backend-support-probe-api-plan.md Outdated
Comment thread docs/backend-support-probe-api-plan.md
Comment thread docs/backend-support-probe-api-plan.md Outdated
Comment thread docs/backend-support-probe-api-plan.md Outdated
Copilot AI review requested due to automatic review settings July 31, 2026 18:10

This comment was marked as outdated.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 31, 2026 18: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.

Review details

Suppressed comments (9)

docs/backend-support-probe-api-plan.md:94

  • The build condition does not match the TypeScript gate this plan proposes to port. isIsoSessionSupported() currently accepts only major === 26300 && minor >= 8553 and explicitly rejects other major builds (sdk/node/src/platform.ts:102-104), whereas >= 26300.8553 includes all later builds. Specify the intended compatibility rule consistently before implementing the Rust detector.
| `isolation_session` | build ≥ 26300.8553 **and** `IsoSessionApp.dll` resolvable **and** feature compiled | build gate lives **only in the TypeScript SDK** | wrong OS builds falsely pass |

docs/backend-support-probe-api-plan.md:65

  • Fix the merged words in “checkwhich.”
| `windows_sandbox` | `isWindowsSandboxAvailable()` runs `dism /online /get-featureinfo `<br>`/featurename:Containers-DisposableClientVM ` and looks for `State : Enabled`<br>if DISM throws (usually non-elevated) it falls back to<br>`fs.existsSync(%SystemRoot%\\System32\\WindowsSandbox.exe)`; result cached. | TypeScript SDK | `dism /online` needs elevation, so a non-elevated caller can't tell *disabled* from<br>*no permission* and drops to the exe-existence checkwhich proves the feature is installed,<br>not that a sandbox VM can boot. (Will move to Rust) |

docs/backend-support-probe-api-plan.md:107

  • Porting only the Windows Sandbox and LXC checks cannot make the function cover “every backend”: this same document leaves Isolation Session, MicroVM, and Hyperlight unreported until separate detectors land (§4.1 and lines 128-129). Limit this step to the currently probe-ready backends or include all missing-detector work in it.
| 1. Consolidate detectors in Rust | Port the two TypeScript-only checks (`windows_sandbox` DISM/feature check, `lxc-ls`) into Rust<br>so `available_backends()` covers every backend. | Each backend has exactly one detector. |

docs/backend-support-probe-api-plan.md:137

  • There is no IsoSessionApp.dll gate in TypeScript to port—the current TypeScript check only queries CurrentBuild/UBR (sdk/node/src/platform.ts:67-104). State that DLL resolvability and compile-feature detection are new Rust checks so the follow-up scope is accurate.
2. `isolation_session` - port the build-number + `IsoSessionApp.dll` gate from TypeScript to Rust.

docs/backend-support-probe-api-plan.md:27

  • The signature does not enforce the canonical-name contract: backend is a public String (and tier is another String below), so Rust consumers cannot match exhaustively and future construction can serialize invalid names despite output tests. Use serde-renamed enums—ideally a concrete-backend enum plus a tier enum—and convert to strings only at the wire boundary.
    pub backend: String,
    /// The highest-isolation tier the host supports for this backend, if the
    /// backend has a tier ladder. `None` for backends with no tiers. The string
    /// values are the canonical `IsolationTier::as_str()` names (not free-form),
    /// and the field is omitted from JSON when `None`.

docs/backend-support-probe-api-plan.md:60

  • This inventory omits two checks already used today: Seatbelt is probed via /usr/bin/sandbox-exec existence (mxc_engine/src/platform.rs:37), and Bubblewrap via bwrap --version (mxc_engine/src/platform.rs:55), with equivalent TypeScript checks. Include both rows and their shallow-check risks; otherwise the stated detector inventory conflicts with the later test plan.

This issue also appears in the following locations of the same file:

  • line 65
  • line 94
  • line 107
  • line 137
Four backends already have *some* presence check, but they are spread across two
layers and vary in how much they actually prove. Documenting them here so the
probe API can reuse the Rust ones and knowingly accept the risk of the shallower
ones.

docs/backend-support-probe-api-plan.md:79

  • appcontainer-dacl is always selected by the policy-free tier walk, but that does not establish the API's promised “can actually run” result. The existing detector only warns when the system-drive and \Device\Null preparations are absent (fallback_detector.rs:248-253), while docs/host-prep.md:38-54 says Tier 3 depends on them and the null-device default is AppContainer-hostile. Either gate availability on those prep facts or narrow the API contract and the “Stock Windows” example to report tier selectability rather than runnable availability.
| `base-container` | BaseContainer API is **usable** (not merely symbol-present) | `fallback_detector::is_base_container_usable()` (the cached wrapper) |
| `appcontainer-bfs` | built with the `tier2_bfs` feature | `cfg!(feature = "tier2_bfs")` |
| `appcontainer-dacl` | always (universal Windows floor) | — |

docs/backend-support-probe-api-plan.md:127

  • The CLI-based Linux checks need a bounded timeout. Both the existing Rust command_succeeds() and TypeScript lxc-ls/bwrap probes wait indefinitely, so calling this API at startup can hang the application if a PATH-resolved executable stalls. Require timeout plus child termination and treat timeout as unavailable.
- On Linux, `bubblewrap` and `lxc` each appear when their check passes (`bwrap --version` / `lxc-ls --version`).

docs/backend-support-probe-api-plan.md:142

  • wxc-exec is Windows-only, and mxc_ffi currently exposes no probe entry point, so this transport cannot project the Linux/macOS backend results described above into the Node SDK. Define a native endpoint for each platform (lxc-exec and mxc-exec-mac as well as wxc-exec), or add a cross-platform binding that Node can actually call.
5. TypeScript projection - make `getPlatformSupport()` read the native probe (`wxc-exec --probe` / `mxc_ffi`)
instead of running its own `dism`/`lxc-ls`, so the two layers can't drift (§4.2, step 2).
  • Files reviewed: 1/1 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread docs/backend-support-probe-api-plan.md Outdated
@huzaifa-d
Huzaifa Danish (huzaifa-d) marked this pull request as ready for review July 31, 2026 18:25
@huzaifa-d
Huzaifa Danish (huzaifa-d) requested a review from a team as a code owner July 31, 2026 18:25
@azure-pipelines

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

The §4.2 step 2 previously said to "extend that JSON to carry the
backend list" via `wxc-exec --probe`. This violates the proposed
read-only contract: `wxc/src/main.rs:750-772` runs
`recover_orphaned_state()` *before* handling `--probe`, which can
restore/prune host DACL state.

Update the plan to:
- Require a side-effect-free transport (e.g. a new `--available-backends`
  mode handled before DACL recovery, or `mxc_ffi`) instead of extending
  `--probe` unchanged.
- Explicitly note the constraint in follow-up work item 5.

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.

Review details

Suppressed comments (8)

docs/backend-support-probe-api-plan.md:94

  • The IsolationSession prerequisite does not match the TypeScript gate this follow-up says to port. sdk/node/src/platform.ts:96-105 accepts only major === 26300 && minor >= 8553; a generic >= 26300.8553 also admits later major builds that the current implementation explicitly rejects. State the exact predicate, or explicitly document and test the intended expansion.
| `isolation_session` | build ≥ 26300.8553 **and** `IsoSessionApp.dll` resolvable **and** feature compiled | build gate lives **only in the TypeScript SDK** | wrong OS builds falsely pass |

docs/backend-support-probe-api-plan.md:107

  • Porting only these two checks cannot make the API cover every backend: this same plan says isolation_session, microvm, and hyperlight remain omitted until separate detectors land. Limit this claim to the two newly consolidated backends and keep the single-detector rationale scoped to backends with landed detectors.
| 1. Consolidate detectors in Rust | Port the two TypeScript-only checks (`windows_sandbox` DISM/feature check, `lxc-ls`) into Rust<br>so `available_backends()` covers every backend. | Each backend has exactly one detector. |

docs/backend-support-probe-api-plan.md:89

  • The “no process launch” criterion conflicts with the planned dism, bwrap --version, and lxc-ls --version subprocesses. If the intended restriction is only that probing must not launch a sandbox workload, say that explicitly; otherwise the proposed detectors do not satisfy this definition of probe-suitable.
Four backends have no *probe-suitable* (cheap, no persistent host mutation, no
process/VM launch) host detector in Rust today, so a truthful availability signal
for them **cannot be built just yet**.

docs/backend-support-probe-api-plan.md:127

  • These subprocess probes need a bounded timeout before they are used by a startup API. The existing Rust Bubblewrap check calls Command::status() without a timeout (src/core/mxc_engine/src/platform.rs:90-99), and the TypeScript LXC/Bubblewrap checks are also unbounded, so a hung executable can block available_backends() indefinitely. Specify and test timeout handling, treating expiry as unavailable.
- On Linux, `bubblewrap` and `lxc` each appear when their check passes (`bwrap --version` / `lxc-ls --version`).

docs/backend-support-probe-api-plan.md:14

  • The stated contract is stronger than the proposed detectors can provide. The document later acknowledges that lxc-ls --version does not prove container privileges, the Windows Sandbox check does not prove a VM can boot, and tier selection does not guarantee a request will run. Either strengthen detection to meet “can actually run”/“not currently usable for any reason,” or consistently describe this as a best-effort prerequisite probe so callers do not treat a positive result as an execution guarantee.

This issue also appears in the following locations of the same file:

  • line 87
  • line 107
Provide a read-only Rust API that reports which containment backends the
current host can actually run. Callers can read at startup
to choose a backend without attempting an execution. 

docs/backend-support-probe-api-plan.md:60

  • This inventory omits two existing Rust presence checks: Seatbelt checks /usr/bin/sandbox-exec at src/core/mxc_engine/src/platform.rs:35-50, and Bubblewrap runs bwrap --version at platform.rs:53-65. Add both rows (including their false-positive risks), otherwise this section does not accurately identify the detectors the new API can reuse.

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

Four backends already have *some* presence check, but they are spread across two
layers and vary in how much they actually prove. Documenting them here so the
probe API can reuse the Rust ones and knowingly accept the risk of the shallower
ones.

docs/backend-support-probe-api-plan.md:65

  • Correct the merged words and punctuation in “checkwhich.”
| `windows_sandbox` | `isWindowsSandboxAvailable()` runs `dism /online /get-featureinfo `<br>`/featurename:Containers-DisposableClientVM ` and looks for `State : Enabled`<br>if DISM throws (usually non-elevated) it falls back to<br>`fs.existsSync(%SystemRoot%\\System32\\WindowsSandbox.exe)`; result cached. | TypeScript SDK | `dism /online` needs elevation, so a non-elevated caller can't tell *disabled* from<br>*no permission* and drops to the exe-existence checkwhich proves the feature is installed,<br>not that a sandbox VM can boot. (Will move to Rust) |

docs/backend-support-probe-api-plan.md:29

  • Option<String> is free-form despite the comment claiming otherwise, so the public Rust API cannot enforce canonical tier values and the claimed structural drift prevention does not hold. Use a typed public enum/newtype (serialized to the existing strings), or make construction private and validate through a canonical constructor.

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

    /// values are the canonical `IsolationTier::as_str()` names (not free-form),
    /// and the field is omitted from JSON when `None`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tier: Option<String>,
  • Files reviewed: 1/1 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Comment thread docs/backend-support-probe-api-plan.md
Comment thread docs/backend-support-probe-api-plan.md
Comment thread docs/backend-support-probe-api-plan.md
Comment thread docs/backend-support-probe-api-plan.md Outdated
| Backend | What a real probe needs | What exists today | Risk if faked |
| --- | --- | --- | --- |
| `windows_sandbox` | DISM/registry check of the *Containers-DisposableClientVM* optional feature | only a private "is the `.exe` on disk" check | reports available when the feature is off → launch fails |
| `isolation_session` | build ≥ 26300.8553 **and** `IsoSessionApp.dll` resolvable **and** feature compiled | build gate lives **only in the TypeScript SDK** | wrong OS builds falsely pass |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

adpa-ms will sessions have an API we can call with our new probe? Just to see if it's available I mean. Possibly could be an Is-Apiset-Available call, assuming they apis are in an apiset that is.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I improved the probe in #761 where it now looks for the API class being registered on the OS instead of checking a build number.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

adpa-ms following up: could sessions expose an apiset-available style check (for example IsApiSetImplemented against the IsolationSession apiset) so the probe can confirm availability cheaply without attempting a real activation? Now that #761 detects the Windows.AI.IsolationSession.Preview IsoSessionOps class registration rather than a build number, a lightweight availability query would shape the isolation_session detector nicely.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This API isn't in an apiset unfortunately. I do not think I can do better than I have put in right now. However, I'm noting this down as a follow up. The isolation session API is also attempting to lift out of the OS, so that may open up some better options down line.

Comment thread docs/backend-support-probe-api-plan.md
Comment thread docs/backend-support-probe-api-plan.md
Comment thread docs/backend-support-probe-api-plan.md
Huzaifa Danish (huzaifa-d) pushed a commit that referenced this pull request Aug 7, 2026
Address @bbonaby's review note (#717): the tier-name strings were hand-written across the appcontainer fallback code. Make IsolationTier the single source of truth in both directions.

- Add IsolationTier::ALL (canonical tier set, strongest-first) and derive a FromStr impl from as_str() via ALL, so the two directions cannot drift and adding a tier is a one-line change to ALL + as_str.
- Remove the ad-hoc test-only parse_force_tier(); the production MXC_FORCE_TIER seam now parses via FromStr.
- Add typed ForceTierGuard::set_tier(IsolationTier) and migrate all 18 valid force-tier call-sites off raw string literals (the one negative test intentionally keeps a raw invalid value).
- Add a round-trip test asserting every ALL tier survives as_str -> FromStr.

The available_backends() probe (already merged here) consumes as_str() with its own drift guard; the CI coverage gate for new tiers/backends is tracked in the follow-up issue.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Address @bbonaby's review notes on #717:
- Define what 'Stock Windows' means (clean install, default optional features -> appcontainer-dacl floor) and link docs/process-container/os-version-support.md near the examples table.

And reflect @adpa-ms's change in #761:
- isolation_session availability is now detected by whether the Windows.AI.IsolationSession.Preview IsoSessionOps API class is registered on the OS (activation-factory resolves), not a build-number gate (26300.8553). Update the probe-gap table row and the follow-up work item accordingly.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 7, 2026 16: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.

Review details

Suppressed comments (7)

docs/backend-support-probe-api-plan.md:95

  • “No process launch” contradicts the proposed bwrap --version and lxc-ls --version detectors, which spawn child processes. Clarify that the constraint is no sandbox workload or VM launch; otherwise those planned detectors are not probe-suitable by this definition.
Four backends have no *probe-suitable* (cheap, no persistent host mutation, no
process/VM launch) host detector in Rust today, so a truthful availability signal
for them **cannot be built just yet**.

docs/backend-support-probe-api-plan.md:115

  • This does not structurally eliminate wire-name drift: AvailableBackend.backend and tier are both String, so producers must still hand-encode the values and serde will serialize any typo unchanged. Use serializable closed enums (a concrete-backend enum and an isolation-tier enum), or make these fields private and expose constructors that derive the strings from those enums.
| 3. Names flow from serde | Backend names (`Containment`) and tier strings (`IsolationTier::as_str()`) are already Rust-serialized;<br>TypeScript consumes them as-is instead of hand-re-encoding. | Removes the wire-name drift class structurally. |

docs/backend-support-probe-api-plan.md:102

  • The proposed Hyperlight check is incomplete. Dispatch supports Hyperlight only on x86_64 (mxc_engine/src/run.rs:351-372), and the runner fails preflight when neither an installed image nor kernel/initrd install source exists (hyperlight/common/src/lib.rs:241-257). Checking only the feature and hypervisor can therefore report an ARM64 build or image-less host as available; include architecture and image/install-source checks.
| `hyperlight` | hypervisor present + feature compiled | nothing | same VM-boot risk |

docs/backend-support-probe-api-plan.md:135

  • WslcSdk::load() only proves that the colocated SDK DLL and exports exist. The execution path immediately calls WslcGetMissingComponents and refuses to run when the WSL package or Virtual Machine Platform is missing (wsl_container_runner.rs:346-365), so this test would advertise wslc on hosts that cannot use it. Require WslcGetMissingComponents to succeed with no missing bits before returning this backend, and update the detector table accordingly.
- `wslc` appears when `WslcSdk::load()` resolves `wslcsdk.dll`; the remaining VM group
(`windows_sandbox`, `isolation_session`, `microvm`, `hyperlight`) never appears until its detector lands.

docs/backend-support-probe-api-plan.md:100

  • The “as of #761” status is stale: that change already adds public is_service_available() activation probing and transports it as probes.isolationSessionAvailable. The remaining gap is moving/reusing that detector behind the new side-effect-free transport, not creating or exposing a Rust probe seam; update this row and follow-up item 2 so they do not schedule completed work.
| `isolation_session` | activation of the in-proc `Windows.AI.IsolationSession.Preview` `IsoSessionOps` runtime class succeeds (the API class is registered on the OS **and** its OS feature gate is on) **and** the backend feature is compiled | as of #761, detection queries whether the API class is registered rather than gating on a build number; a `CLASS_E_CLASSNOTAVAILABLE` / `REGDB_E_CLASSNOTREG` activation failure means unavailable | none for false-availability now — a machine without the API registered fails activation cleanly; still needs a cheap probe seam so callers don't have to attempt a real activation |

docs/backend-support-probe-api-plan.md:66

  • This inventory is incomplete: mxc_engine::platform_support() already checks bwrap --version and /usr/bin/sandbox-exec (platform.rs:35-66), and the TypeScript layer has the same checks. Add Bubblewrap and Seatbelt so the design records the existing detectors it plans to reuse.

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

Four backends already have *some* presence check, but they are spread across two
layers and vary in how much they actually prove. Documenting them here so the
probe API can reuse the Rust ones and knowingly accept the risk of the shallower
ones.

docs/backend-support-probe-api-plan.md:71

  • Correct the merged words in this sentence.
| `windows_sandbox` | `isWindowsSandboxAvailable()` runs `dism /online /get-featureinfo `<br>`/featurename:Containers-DisposableClientVM ` and looks for `State : Enabled`<br>if DISM throws (usually non-elevated) it falls back to<br>`fs.existsSync(%SystemRoot%\\System32\\WindowsSandbox.exe)`; result cached. | TypeScript SDK | `dism /online` needs elevation, so a non-elevated caller can't tell *disabled* from<br>*no permission* and drops to the exe-existence checkwhich proves the feature is installed,<br>not that a sandbox VM can boot. (Will move to Rust) |
  • Files reviewed: 1/1 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@bbonaby Branden Bonaby (bbonaby) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

FYI: Gudge (@MGudgin) would probably be interested in seeing how we implement this

Comment on lines +196 to +201
### 7.7 `base-container` detection caching

`fallback_detector::is_base_container_usable()` caches its result in a
`OnceLock`. So "fresh detection on every call" is not fully achievable for the
`base-container` tier, and a cached `true` can go **stale** if BaseContainer
enablement changes mid-process. Both are accepted and documented rather than

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

note: Richie added a check directly in base_container_runner now I believe that you can use.

@azure-pipelines

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

@azure-pipelines

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

@huzaifa-d
Huzaifa Danish (huzaifa-d) merged commit f415541 into main Aug 7, 2026
61 of 63 checks passed
@huzaifa-d
Huzaifa Danish (huzaifa-d) deleted the user/modanish/backend-probe-api branch August 7, 2026 17:48
Huzaifa Danish (huzaifa-d) pushed a commit that referenced this pull request Aug 7, 2026
available_backends() (introduced by this PR) is the host-capability probe; it
already reports the broad set (lxc, windows_sandbox, isolation_session, wslc).
platform_support() must stay limited to the backends mxc_sdk::spawn_sandbox can
actually launch (dispatch.rs: seatbelt, bubblewrap, processcontainer, wslc), or a
caller picking an advertised method gets a guaranteed UnsupportedContainment
error. This PR had wrongly expanded platform_support() to add lxc (Linux) and
windows_sandbox + isolation_session (Windows); revert those arms and the field
doc to the launchable-only contract (per #717 design 7.1). The new drift tests
are kept. Addresses SohamDas2021 / Copilot review.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Huzaifa Danish (huzaifa-d) added a commit that referenced this pull request Aug 7, 2026
#725)

* feat(engine): port TS host detectors (lxc, windows_sandbox) to Rust

Port the two TypeScript-only host-availability detectors into Rust and fold their results into mxc_engine::platform::platform_support(), broadening its contract from 'backends mxc-sdk can launch' to 'host-available backends' (Phase 1 of the backend-support-probe plan, PR #717).

- Add lxc_common::availability::is_lxc_available() (shallow 'lxc-ls --version' probe).
- Add windows_sandbox_lifecycle::availability::is_windows_sandbox_available() (DISM State:Enabled with a 10s timeout, WindowsSandbox.exe fallback when DISM can't run; invokes the absolute System32\dism.exe path).
- platform_support(): Linux arm reports lxc and/or bubblewrap; Windows arm adds windows_sandbox alongside processcontainer. Update doc comments to host-capability wording.
- Add drift-guard tests tying reported literals to Containment wire names and asserting the live platform_support() output only contains real wire names.
- Loosen the locked mxc-sdk platform_support tests for the broadened contract.

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

* feat(engine): add available_backends() host-capability probe API

Add mxc_engine::probe with the read-only available_backends() API from the backend-support-probe plan (PR #717, Phase 2). Reports only the containment backends the current host can run, each with its effective isolation tier when it has a tier ladder.

- AvailableBackend { backend, tier: Option<String> } serializes to camelCase JSON with tier omitted (never null) when the backend has no tier ladder.
- available_backends() has per-platform arms reusing the landed detectors: macOS -> seatbelt; Linux -> bubblewrap/lxc; Windows -> processcontainer (with effective tier) + windows_sandbox + wslc (feature-gated). Empty Vec is a normal result, not an error.
- select_tier() is a pure precedence fn (base-container -> appcontainer-bfs -> appcontainer-dacl floor), unit-testable without a real host or the tier2_bfs feature.
- Re-exported from mxc_engine and the public mxc-sdk.
- 8 unit tests: serde shape (tier omitted vs present), host + unconditional wire-name drift guards, canonical-tier drift guard, Windows processcontainer-always-with-tier, tier precedence, non-Windows processcontainer absence.

Stacked on the host-detector port (PR #725); the standalone detectors it reuses land there.

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

* fix(windows_sandbox): address Copilot review on host detectors

- Resolve dism.exe / WindowsSandbox.exe via GetSystemDirectoryW instead of the attacker-controllable %SystemRoot% env var (UAC inherits an unelevated parent's environment, so an env-derived path let a standard user point the probe at a planted binary). Mirrors the src/host/plm/src/wpr_path.rs pattern; falls back to the C:\\Windows\\System32 literal only on an outright Win32 failure.
- Pass DISM's /English global option so the parsed 'State : Enabled' tokens are not localized on non-English Windows (previously an enabled Sandbox could be reported unavailable there, since a successful DISM run also skips the exe fallback).
- Soften platform_support() docs: available_methods is the currently detected subset, not an exhaustive capability list — backends without a Rust detector yet (notably isolation_session) are omitted even when the host could run them, so absence is not proof a backend cannot run.

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

* docs: trim comments; drop DISM from windows_sandbox probe

Trim the module-level narratives and over-explained item docs across the host detectors and probe to concise, necessary comments; remove duplicated phrasing (e.g. 'universal floor') within and across files.

Also drop the DISM query from the Windows Sandbox probe: dism /online requires elevation and this probe only ever runs unelevated (wxc-exec does not self-elevate), so DISM always failed through to the WindowsSandbox.exe existence check anyway. Detection is now exe-only, which also removes the subprocess-launch attack surface (only a .exists() remains). A short comment records why DISM is skipped.

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

* refactor(appcontainer): centralize isolation-tier name strings

Address @bbonaby's review note (#717): the tier-name strings were hand-written across the appcontainer fallback code. Make IsolationTier the single source of truth in both directions.

- Add IsolationTier::ALL (canonical tier set, strongest-first) and derive a FromStr impl from as_str() via ALL, so the two directions cannot drift and adding a tier is a one-line change to ALL + as_str.
- Remove the ad-hoc test-only parse_force_tier(); the production MXC_FORCE_TIER seam now parses via FromStr.
- Add typed ForceTierGuard::set_tier(IsolationTier) and migrate all 18 valid force-tier call-sites off raw string literals (the one negative test intentionally keeps a raw invalid value).
- Add a round-trip test asserting every ALL tier survives as_str -> FromStr.

The available_backends() probe (already merged here) consumes as_str() with its own drift guard; the CI coverage gate for new tiers/backends is tracked in the follow-up issue.

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

* feat(engine): add isolation_session host detection (registration-based)

Add isolation_session to the host-availability surfaces using @adpa-ms's registration-based approach from #761: availability is whether the in-proc Windows.AI.IsolationSession IsoSessionOps WinRT class is registered on the OS (its activation factory resolves), not a build-number gate.

- New isolation_session_common::availability::is_isolation_session_available(): attempts IsoSessionOps activation (CoInitialize MTA, balanced), OnceLock-cached, elevation-free. Pure available_from() split from the COM probe for unit testing (CLASS_E_CLASSNOTAVAILABLE / REGDB_E_CLASSNOTREG and any other activation failure map to unavailable).
- Wire it into both Windows host-capability surfaces, gated behind the engine's isolation_session feature: available_backends() (probe) and platform_support(). This closes the previously-documented Rust/TS parity gap where TS reported isolation_session but Rust did not.
- Add isolation_session to the probe's EMITTABLE_BACKENDS drift guard and loosen the mxc-sdk Windows platform_support test to allow it.

Validated with the feature on and off: fmt clean, clippy -D warnings clean (default, isolation_session, and wslc+tier2_bfs+isolation_session), and tests green.

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

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* docs: trim comments in the windows_sandbox + isolation_session probes

Keep only comments that explain non-obvious code (why exe-not-DISM, the GetSystemDirectoryW env-spoof rationale, the Win32 buffer-grow retry, and the COM init/uninit balancing); drop the rest. No behavior change.

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

* docs: restore platform_support module rationale

Restore the 'why this exists' note (in-process host discovery, decoupled from the TypeScript SDK; lives in the engine so the SDK and executor binaries share one impl) that an earlier comment-trim over-aggressively removed. Kept concise.

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

* docs: restore pre-existing platform.rs comments simplified by this PR

Per review: only prune comments in files/comments this PR introduces. Restore the pre-existing comments an earlier trim pass simplified in platform.rs:
- module doc (full 'stop depending on the TypeScript SDK' + 'lives alongside dispatch.rs' rationale),
- platform_support() fn doc (the wxc-exec --probe and wslc/feature specifics; dropped only the now-inaccurate 'restricted to backends mxc-sdk can run' clause the broadened contract invalidated, pointing to the field doc instead),
- the Linux-arm bwrap / MIN_BWRAP_VERSION note (kept, plus the new lxc line).

The two sdk_helpers.rs comments were left corrected rather than reverted: this PR's behavior change made them factually wrong (LXC is now reported; WSLC is no longer the only other Windows backend).

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

* refactor(wxc_common): extract shared system_directory() helper

Addresses jsidewhite's review on #725: GetSystemDirectoryW + grow-retry was duplicated in windows_sandbox availability.rs and plm/wpr_path.rs. Consolidate into wxc_common::system_dir and route both call-sites through it.

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

* fix(engine): address review on host detectors (wslc probe, tier de-drift, COM guard)

- probe.rs: report wslc via wslc_common::is_available() (WSL2 + runtime check) instead of WslcSdk::load(), matching platform_support() and the runner preflight (SohamDas).
- fallback_detector.rs: generate IsolationTier's enum, ALL, as_str, and FromStr from one macro list so the mapping is exhaustive both ways and cannot drift (SohamDas).
- isolation_session/availability.rs: wrap CoInitializeEx/CoUninitialize in an RAII ComApartment guard so a panic in IsoSessionOps::new() still balances COM init (SohamDas).

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

* fix(windows_sandbox): route runner preflight through the trusted availability probe

check_sandbox_available() used an env-spoofable %SystemRoot% path while capability reporting used GetSystemDirectoryW. Delegate the preflight to is_windows_sandbox_available() so reporting and execution agree and the runner is no longer environment-spoofable (Copilot review).

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

* fix(windows_sandbox): launch WindowsSandbox.exe from the trusted system path

The daemon launched the sandbox binary by bare name, relying on the executable
search order (app dir/CWD) — an attacker-planted binary could run in its place,
outside the sandbox. Resolve it under the trusted system_directory()
(GetSystemDirectoryW), matching the detection path hardened elsewhere in this PR.
Surfaced by adversarial review.

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

* fix(engine): keep platform_support() limited to SDK-launchable backends

available_backends() (introduced by this PR) is the host-capability probe; it
already reports the broad set (lxc, windows_sandbox, isolation_session, wslc).
platform_support() must stay limited to the backends mxc_sdk::spawn_sandbox can
actually launch (dispatch.rs: seatbelt, bubblewrap, processcontainer, wslc), or a
caller picking an advertised method gets a guaranteed UnsupportedContainment
error. This PR had wrongly expanded platform_support() to add lxc (Linux) and
windows_sandbox + isolation_session (Windows); revert those arms and the field
doc to the launchable-only contract (per #717 design 7.1). The new drift tests
are kept. Addresses SohamDas2021 / Copilot review.

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

* fix(lxc): bound and cache the lxc-ls availability probe

is_lxc_available() ran `lxc-ls --version` with a blocking status() and no cache,
so a hung lxc-ls would block discovery indefinitely and every call re-spawned it
(unlike the isolation_session and windows_sandbox probes). Cache the result in a
OnceLock and wait with a bounded deadline, killing and reaping the child on
timeout. Addresses SohamDas2021 review.

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

* fix(engine): derive probe backend names from the enum; gate BFS tier on bfscfg.exe

- #7 (SohamDas2021): available_backends() pushed hardcoded string literals, and
  the drift tests only checked that a separate hardcoded list was valid wire
  names — not that the pushed literals matched. Derive every pushed name from
  ContainmentBackend::wire_name() so it can't be typo'd, and make the emittable
  test iterate the enum so it verifies ContainmentBackend::wire_name() agrees
  with the canonical wire::Containment serde names.

- #9 (SohamDas2021): select_tier() reported AppContainerBfs from the tier2_bfs
  build flag alone, but detect() also requires bfscfg.exe on disk for a
  policy-carrying request (else it falls to DACL). Add a bfscfg_available()
  probe and require both the feature and a resolvable bfscfg.exe before naming
  BFS, so the reported tier ceiling matches what a real request achieves.

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

* docs(mxc-sdk): add host-backend discovery usage guidance + before/after

Documents platform_support() vs available_backends() (which to use when), a
usage example, and the tier-ceiling caveat, plus the before/after framing.
Addresses jsidewhite review on #725.

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

* fix(lxc): drop unread exit code from LxcLsOutcome::ExitedFailure

The decision only distinguishes success from every other outcome, so the
Option<i32> exit code was never read (dead code under -D warnings). Make
ExitedFailure a unit variant. Addresses Copilot review.

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

* fix(engine): report BFS tier ceiling from tier2_bfs alone, not bfscfg.exe

Reverts the bfscfg.exe gating added earlier in this PR. The probe's tier is a
CEILING (strongest reachable for some request), not a per-request value:
detect() returns AppContainerBfs for a no-filesystem-policy request without
bfscfg.exe, so the ceiling on any tier2_bfs build is BFS regardless of bfscfg.
Gating the ceiling on bfscfg under-reported it to appcontainer-dacl on a
tier2_bfs host lacking bfscfg. bfscfg only decides whether a policy-carrying
request stays at BFS or drops to DACL, which is a request-time dispatch concern.
Addresses Copilot review; supersedes the earlier response to the bfscfg comment.

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

* test(mxc-sdk): fence platform_support() to the SDK-launchable set

The platform_support consistency tests were loosened earlier in the PR when it
temporarily advertised lxc / windows_sandbox / isolation_session; after those
moved to available_backends() the tests were never re-tightened, so they still
permitted the excluded backends and a regression re-advertising one would pass
(the Linux loop also passed vacuously on a host with no methods).

- Linux: assert available_methods == exactly ["bubblewrap"] (lxc excluded).
- Windows: keep processcontainer-first, then assert every method is
  processcontainer | wslc, explicitly excluding windows_sandbox /
  isolation_session.

Addresses SohamDas2021 review.

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

---------

Co-authored-by: Huzaifa Danish <modanish@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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.

6 participants