Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4a9c362
Give wxc-exec a typed error and warning contract
theelliotm Oct 12, 2026
4f6c81d
Classify every backend failure by which side can act on it
theelliotm Oct 12, 2026
1067ca9
Skip capture assertions where the host reports backend_unavailable
theelliotm Oct 12, 2026
f9d03b9
Report a tier the host could not build as backend_unavailable
theelliotm Oct 13, 2026
ad5b809
Keep an unrepresentable policy field a rejection, not an unavailable …
theelliotm Oct 13, 2026
0a3b502
Merge remote-tracking branch 'origin/main' into user/emichlin/cross-b…
theelliotm Oct 13, 2026
1e1923d
Route audit cleanup diagnostics through the warning contract
theelliotm Oct 13, 2026
a39b1d0
Apply one classification to the paths that still disagreed
theelliotm Oct 13, 2026
9b7a541
Merge remote-tracking branch 'origin/main' into user/emichlin/cross-b…
theelliotm Oct 14, 2026
fcb5c5a
Remove Seatbelt sandbox-exec availability check
theelliotm Oct 15, 2026
04d2170
Changes in response to Gudge's feedback
theelliotm Oct 15, 2026
387df58
Centralize phase-aware ScriptResponse construction
theelliotm Oct 15, 2026
5267817
Fix engine warning tests failing on Windows and macOS
theelliotm Oct 16, 2026
bc01847
Address Copilot review: timeout phase, warning line start, lifecycle …
theelliotm Oct 16, 2026
a132298
Merge remote-tracking branch 'origin/main' into user/emichlin/cross-b…
theelliotm Oct 16, 2026
c1c3b29
fixes: teardown outcome shadowing execoutcome, ScriptResponse default…
theelliotm Oct 16, 2026
ab78432
Classify Seatbelt profile host errors and emit dry-run error envelopes
theelliotm Oct 16, 2026
2ed5f6f
Keep IsolationSession timeouts typed and envelopes on fresh lines
theelliotm Oct 16, 2026
cce533e
Merge origin/main
theelliotm Oct 16, 2026
3afba24
Report relayed lifecycle exec timeouts as backend errors
theelliotm Oct 16, 2026
c33d632
Merge origin/main
theelliotm Oct 20, 2026
7d25f28
Classify unexaminable filesystem policy paths as launch failures
theelliotm Oct 20, 2026
5aa9ce7
Merge origin/main into user/emichlin/cross-backend-error-contract
theelliotm Oct 21, 2026
c6759be
Box the WSLc session-create failure response
theelliotm Oct 21, 2026
42d5259
Merge origin/main into cross-backend-error-contract
theelliotm Oct 23, 2026
9023780
Use a refused runtime proxy in the LXC refusal-phase test
theelliotm Oct 23, 2026
7001ad6
Report host faults during policy path checks as backend errors
theelliotm Oct 23, 2026
7264314
Condense error-classification text in versioning.md into a table
theelliotm Oct 23, 2026
4fb15f6
Scope fmt::Write import to the wslc arm so non-wslc builds compile
theelliotm Oct 23, 2026
c4f5192
Merge origin/main into user/emichlin/cross-backend-error-contract
theelliotm Oct 23, 2026
84776f9
Report host I/O faults while resolving deniedPaths as backend errors
theelliotm Oct 23, 2026
da7afec
Merge origin/main into user/emichlin/cross-backend-error-contract
theelliotm Oct 27, 2026
909d0cb
Keep SDK v1 error codes where v2 codes would break callers
theelliotm Oct 27, 2026
995d669
Merge origin/main into user/emichlin/cross-backend-error-contract
theelliotm Oct 27, 2026
c7bfbe5
Require a typed timeout error from the IsolationSession host test
theelliotm Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/backends/bwrap/bubblewrap-backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,18 @@ isolated wrapper that removes `--die-with-parent`, then checks explicit
kill, timeout, and handle-drop cleanup. This reproduces the absence of
parent-death protection during startup without relying on a scheduling race.

## Troubleshooting

These failures carry the `backend_error` code and exit `-1`.

| Message | Meaning |
|---|---|
| `Bubblewrap (bwrap) is not installed or not on PATH` / version too old | The host has no usable Bubblewrap; install `bubblewrap` 0.5+ |
| `requires 'slirp4netns' on PATH` / `requires unprivileged user and network namespaces` | The host cannot provide the sandbox network |
| `script_code is empty` / `must not carry credentials` / an egress or proxy refusal | The request is at fault; change it and retry |
| `failed to spawn bwrap` / `failed to allocate PTY` / proxy namespace start failure | The request was accepted but the launch faulted |
| `failed to wrap stdio pipes` | The sandbox started and then faulted |

## Limitations

- **Linux only** β€” Bubblewrap requires Linux kernel namespaces
Expand Down
13 changes: 10 additions & 3 deletions docs/backends/hyperlight/hyperlight-backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ to the snapshot between calls.

| Aspect | Behaviour |
|---|---|
| Exit code | The script's: `sys.exit(N)`, `process.exit(N)` or `exit N` gives `N`, an uncaught exception gives `1`, a runner error gives `-1` with the reason in `error_message` |
| Exit code | The script's: `sys.exit(N)`, `process.exit(N)` or `exit N` gives `N`, an uncaught exception gives `1`. A refused request gives `1` and a host or runtime fault gives `-1`, with the reason in `error_message` |
| Timeout | `process.timeout` bounds the run; a guest that overruns is interrupted and the run reports `execution timed out` |
| stdout / stderr | Inherited by the guest; `ScriptResponse.standard_out` stays empty, so capture at the process level |

Expand Down Expand Up @@ -155,11 +155,18 @@ contain whitespace, `:` or brackets, and two mounts may not share one.

## Troubleshooting

Failures carry an error code: `policy_validation` (exit 1) when the request
asks for something the backend refuses, `backend_unavailable` (exit -1) when
the host cannot provide the backend at all, and `backend_error` (exit -1) for
setup and runtime faults.

| Message | Meaning |
|---|---|
| `no hyperlight image found for the <runtime> runtime` | No home holds that runtime; run `--setup-hyperlight=<runtime>`, or set `MXC_HYPERLIGHT_HOME` |
| `hyperlight policy error: …` | The request is at fault (`policy_validation`); change it and retry |
| `no hyperlight image found for the <runtime> runtime` | No home holds that runtime (`backend_unavailable`); run `--setup-hyperlight=<runtime>`, or set `MXC_HYPERLIGHT_HOME` |
| `holds a rootfs from another hyperlight-unikraft release` | The directory predates the crate the binary was built with; `--setup-hyperlight=<runtime>` rebuilds it |
| `Hyperlight requires KVM` / `Hyperlight requires Windows Hypervisor Platform` | No hypervisor for this process (error code `backend_unavailable`): on Linux `/dev/kvm` is missing or not readable and writable, on Windows WHP is not enabled |
| `Hyperlight requires KVM` / `Hyperlight requires Windows Hypervisor Platform` | No hypervisor for this process (`backend_unavailable`): on Linux `/dev/kvm` is missing or not readable and writable, on Windows WHP is not enabled |
| `hyperlight setup error: …` / `hyperlight runtime error: …` | The run was accepted but faulted (`backend_error`) |
| `execution timed out after N s` | The script overran `process.timeout` |

Run with `--debug` to see the home chosen, restore and call timings, and
Expand Down
12 changes: 12 additions & 0 deletions docs/backends/lxc/lxc-backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,18 @@ streaming in a long-lived service.
sudo lxc-checkconfig
```

## Troubleshooting

These failures carry the `backend_error` code and exit `-1`.

| Message | Meaning |
|---|---|
| `the LXC runtime is not available on this host` | `lxc-ls --version` did not run; install the LXC tools |
| `sandboxes can only be launched on Linux` | Wrong OS for the backend |
| `distribution and release are required` / a proxy, egress or inherited-stdio refusal | The request is at fault; change it and retry |
| `Failed to create container` / `Failed to start container` / `container '…' is already in use` | The request was accepted but the launch faulted |
| `did not receive an IPv4 address` / `Failed to discover the container init PID` | The container started and then faulted before the script ran |

## Limitations

- **The workload can send only ICMP Echo.** A `protocol: "icmp"` rule lets every
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,13 +496,11 @@ State-aware exec (and other phases) use OS-level cancellation in v1:
- On the `wxc-exec` route, cancellation is process termination.
- The agent process's pipes EOF, the relay threads exit.
- The OS-side service's per-process timer (set from
`process.timeout`) reaps the agent if the runner does not. On the
streaming path that timer is armed with a **margin** past the caller's
deadline, so it acts purely as a watchdog: the service kills with an
ordinary exit code (the host suite pins it as exit code 1), so if it
fired first a genuine timeout would be indistinguishable from a normal
exit and could not be reported as one. The run-to-completion path arms
it unchanged β€” it has no timeout channel to report through.
`process.timeout`) reaps the agent if the runner does not. That timer is
armed with a **margin** past the caller's deadline, so it acts purely as
a watchdog: the service kills with an ordinary exit code, so if it fired
first a genuine timeout would be indistinguishable from a normal exit
and could not be reported as one.
- The runner's existing 3-tier shutdown (`CloseStandardInput` β†’
`SendCtrlClose` β†’ `Terminate`) handles the timeout case from inside the
agent process before returning.
Expand Down
11 changes: 7 additions & 4 deletions docs/development/architecture/container-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,9 +616,9 @@ diagnostic. The rejection is recorded through the same `ConfigRejected` audit pa
Diagnostics are never duplicated to stdout, and lifecycle stderr is not gated on
`--debug`.

Without `--operation`, `wxc-exec` accepts only one-shot requests. Input-source and
pre-parse failures on that path retain the legacy primary diagnostic on stderr and do
not emit a lifecycle envelope. Raw SDK and FFI lifecycle calls do not use this CLI
Without `--operation`, `wxc-exec` accepts only create-and-run requests. Input-source
and pre-parse failures on that path emit a typed `{error}` envelope on stderr rather
than the lifecycle envelope on stdout. Raw SDK and FFI lifecycle calls do not use this CLI
stream protocol; they return their status and error data through their binding result
surfaces.

Expand Down Expand Up @@ -671,7 +671,10 @@ because a script's stderr may not end in a newline.
`code` and `message` are always present. `code` is the machine-readable category a
consumer branches on; `message` is the human-readable description, and for a failure
raised by an underlying platform API it is that API's own message, passed through
verbatim rather than concatenated with the other fields.
verbatim rather than concatenated with the other fields. The executor's own exit
code follows `code`: a refused request exits `1`, and an unavailable host or a
runtime fault exits `-1`. See the error contract in
Comment thread
theelliotm marked this conversation as resolved.
[versioning.md](versioning.md#error-contract).

The three optional named fields carry structured failure detail. `operation` and
`nativeCode` describe an underlying platform call; `remediation` describes the failure:
Expand Down
43 changes: 43 additions & 0 deletions docs/development/architecture/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,49 @@ Negotiation failures are **typed and actionable** β€” never a silent fallback:
unsupported field. Security policy is deterministic β€” no relaxation, no fuzzy
fallback.

Every executor failure is classified by **which side can act on it**, and that
classification drives both the wire code and the process exit code:

| Side at fault | Failure phase | Wire code | Exit code |
|---|---|---|---|
| The request β€” a different request succeeds | `Rejected` | `policy_validation` | `1` |
| The host β€” no request change helps | `BackendUnavailable` | `backend_unavailable` | `-1` |
| The runtime, during an accepted run | all other phases | `backend_error` | `-1` |
Comment thread
Copilot marked this conversation as resolved.

Common cases:

| Failure | Wire code |
|---|---|
| Policy field the backend cannot enforce (e.g. ProcessContainer `enumeratePaths`, `deniedPaths`, LPAC) | `backend_unavailable`ΒΉ |
| Policy path the invoking user cannot access | `backend_error`ΒΉ |
| Host fault while checking a policy path (I/O error, unresolvable path) | `backend_error` |
| Missing host capability (e.g. no native PSEC, no denial-capture provider) | `backend_unavailable` |
| Backend not compiled into this build | `unsupported_containment`ΒΉ |
| Experimental backend without the experimental opt-in | `backend_unavailable` |
| Backend for another OS: create-and-run / container lifecycle operations | `unsupported_containment` / `backend_unavailable` |

Unsupported policy is never silently dropped, and no failure above starts the
run. The phase-specific codes `malformed_request`, `unsupported_containment`,
`unsupported_phase`, and `malformed_id` also exit `1`.

ΒΉ Kept for SDK v1 compatibility, as are some backend refusals that still report
`backend_error`. See the [SDK v2 error codes plan](../plans/sdk-v2-error-codes.md).

Envelope lines share stderr with the workload's own output, so a workload can
print text that looks like an envelope. Treat stderr envelopes as diagnostics,
not as an authenticated result; the exit code and a held `ScriptResponse` are
authoritative.

`ScriptResponse` carries an optional `warnings` array of diagnostic strings.
Warnings are advisory and never change the exit code; they are omitted from the
wire entirely when a run produced none. The serialized response and the
executor's stderr stream are two distinct shapes of the same warnings: on the
response they are a `warnings: string[]` field, while an executor writes each
one to stderr as its own line-delimited JSON object,
`{"warning":{"message":"..."}}`, matching the error envelope
`{"error":{"code":"...","message":"..."}}`. A caller reading stderr must parse
the per-line objects; the array exists only on a response it holds directly.

The MXC ↔ OS contract therefore reports: which feature failed, whether it was a
version mismatch or a runtime/capability unavailability (e.g. Hyper-V off), and
what the user should do (upgrade OS, enable feature, change the config).
Expand Down
41 changes: 41 additions & 0 deletions docs/development/plans/sdk-v2-error-codes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!--
Copyright (c) Microsoft Corporation.
Licensed under the MIT License.
-->

# SDK v2 Error Codes

> **Audience:** MXC developers

> **Status:** WIP, tracked by [#1595](https://github.com/microsoft/mxc/issues/1595)

Failures are meant to be classified by the side that can fix them: the request
(`policy_validation`, exit `1`), the host (`backend_unavailable`, exit `-1`), or
the runtime (`backend_error`, exit `-1`). See the
[error contract](../architecture/versioning.md#error-contract).

The sites below still report their SDK v1 code, because changing it would break
published SDK callers. Each one has a `// SDK v2:` comment that names its
intended code. Move them together in the next major SDK version.

| Site | SDK v1 | SDK v2 |
|---|---|---|
| Shared validator: empty script, `enumeratePaths` off ProcessContainer | `backend_error` | `policy_validation` |
| Policy path checks (`FilesystemPolicyError::into_response`) | `backend_error` | `policy_validation`, or `backend_error` with phase `LaunchFailed` for host faults |
| Backend not compiled in (`mxc_engine` `run` and `dispatch`) | `unsupported_containment` | `backend_unavailable` |
| ProcessContainer: unenforceable field (`deniedPaths`, `enumeratePaths`, LPAC) | `backend_unavailable` | `policy_validation` |
| ProcessContainer: PSEC baseline unsupported | `policy_validation` | `backend_unavailable` |
| Bubblewrap: request refusals | `backend_error` | `policy_validation` |
| Bubblewrap: missing or too-old `bwrap`, no network support; proxy lookup failure | `backend_error` | `backend_unavailable`; phase `LaunchFailed` |
| LXC: request refusals | `backend_error` | `policy_validation` |
| LXC: tooling missing, streaming off Linux | `backend_error` | `backend_unavailable` |
| Seatbelt: refusals (`rejected_response`) | `backend_error` | `policy_validation` |
| WSLC: policy-path host faults | `policy_validation` | `backend_error` |
| IsolationSession one-shot: lifecycle refusals, `IsolationSessionError` conversion | `backend_error` | `policy_validation` / `backend_unavailable` |
| IsolationSession one-shot: `validate` and launch failures always use phases `Rejected` and `LaunchFailed` | as forced | the phase from the underlying error |
| IsolationSession relayed exec timeout | exit code `-1` | `backend_error` "script timed out" |

## Remaining work

- Switch each site to its SDK v2 code behind the v2 SDK surface, keeping v1 callers on the v1 codes.
- Update the Rust, .NET and Node references under `docs/api-reference/`, the backend guides and the host test scripts under `tests/scripts/`.
Loading
Loading