diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index dde7e2db2..b2ec44d7a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -118,6 +118,12 @@ Update documentation in the same change when behavior changes: Do not duplicate detailed backend behavior here. Keep the canonical explanation in the subsystem documentation. +### Terminology + +- In consumer docs and public Rustdoc, replace "state-aware" with "container lifecycle operations" ("manual lifecycle operations" for caller responsibility), "one-shot" with "create-and-run execution", and "exact contract" with "contract". +- Use plain English and explicit type, namespace, field, and format names (e.g. "MXC request JSON"). Avoid "wire type", unqualified "raw JSON", "native request", "closed exclusion", "closes over policy", "operation-neutral", and "pinned logging". +- Preserve code identifiers. Follow the [developer wording guide](../docs/development/glossary.md); define retained jargon in the [consumer glossary](../docs/glossary.md) or developer glossary. + ## SDK API consistency - Publish every SDK operation, type, probe, discovery API, telemetry API, and helper only through a supported versioned (V*) namespace/module/entrypoint. diff --git a/README.md b/README.md index 207db6054..1e5a99e60 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ behind a unified containment model and typed SDKs. - **Network policy**: Proxy support, outbound controls, and backend-dependent host filtering - **UI policy**: Clipboard, display, and GUI access controls -- **State-aware lifecycle**: Provision, start, execute, stop, and deprovision +- **Container lifecycle operations**: Provision, start, execute, stop, and deprovision persistent containers -- **Rust, .NET, and Node SDKs**: Versioned APIs for one-shot and state-aware - execution +- **Rust, .NET, and Node SDKs**: Versioned APIs for create-and-run execution and + container lifecycle operations - **Diagnostics**: Tools to understand access-denied failures in a container ## What is MXC? @@ -156,7 +156,7 @@ used by the Node SDK. Build prerequisites are: -- [Rust](https://rustup.rs/), pinned to version **1.93** by +- [Rust](https://rustup.rs/), required version **1.93** as specified by `src/rust-toolchain.toml` - Node.js 24 or later and npm - The platform toolchain and prerequisites described by the selected backend @@ -187,6 +187,7 @@ build.bat --all # Release build for current architecture | Document | Repository location | Purpose | |---|---|---| +| Terminology | [`docs/glossary.md`](docs/glossary.md) | Plain-English definitions of consumer terms | | SDK samples | [`samples/`](samples/README.md) | Runnable Rust, .NET, and Node scenarios | | SDK API reference | [`docs/api-reference/`](docs/api-reference/README.md) | Supported V1 operations and types | | Container lifecycle | [`docs/container-lifecycle.md`](docs/container-lifecycle.md) | Persistent container lifecycle overview | diff --git a/docs/api-reference/README.md b/docs/api-reference/README.md index 8a8e0bed5..4638c6171 100644 --- a/docs/api-reference/README.md +++ b/docs/api-reference/README.md @@ -2,11 +2,13 @@ > **Audience:** MXC consumers -These references describe the supported V1 SDK authoring surfaces: operation +These references describe the supported V1 SDK APIs: operation signatures, public types, fields, and containment choices. They complement the SDK READMEs and backend guides; they do not replace native policy validation or certify backend availability. +See the [consumer glossary](../glossary.md) for terminology. + | SDK | Reference | Public entrypoint | |---|---|---| | Rust | [V1](rust/v1/README.md) | `mxc_sdk::v1` | @@ -36,8 +38,9 @@ Results and live handles expose warnings and optional execution metadata. Provision metadata is backend-specific; each language's type reference lists the supported fields. -Containment selects the backend. Shared restrictions live on the request; -backend-specific authoring uses `*Config` types. Omitted environment input +`ContainerRequest` selects containment and carries shared restrictions; +backend-specific settings use `*Config` types listed in each language's type +reference. Omitted environment input uses backend defaults, whereas an explicitly empty environment is preserved. Filesystem discovery helpers follow the same distinction for their host environment input. Native validation decides which policies the backend can diff --git a/docs/api-reference/dotnet/v1/api.md b/docs/api-reference/dotnet/v1/api.md index b238fee10..101f01382 100644 --- a/docs/api-reference/dotnet/v1/api.md +++ b/docs/api-reference/dotnet/v1/api.md @@ -17,12 +17,12 @@ Callers do not supply JSON or a schema version. Creation takes `ContainerRequest` and operation options. Existing-container execution takes the `ContainerId` returned by provision, `ExecutionRequest`, -and operation options. One-shot PTY support covers IsolationSession, Bubblewrap, +and operation options. Create-and-run PTY support covers IsolationSession, Bubblewrap, LXC, and Seatbelt direct execution. Existing-container PTY support remains IsolationSession-only. Seatbelt PTY rejects `guiAccess` and legacy `launchMethod: "open"`. Terminal handles give the caller explicit input, output, resize, and process ownership; there -is no separate public attached-console or raw-JSON launch API. +is no separate public attached-console launch API or API accepting ["MXC request JSON"](../../../schema.md#mxc-request-json). ## Discovery and validation diff --git a/docs/api-reference/dotnet/v1/types.md b/docs/api-reference/dotnet/v1/types.md index e3d47ab65..c92c8f0db 100644 --- a/docs/api-reference/dotnet/v1/types.md +++ b/docs/api-reference/dotnet/v1/types.md @@ -174,7 +174,7 @@ public sealed class ContainerRequest ## `Microsoft.Mxc.Sdk.V1.Containment` -Choose one of the SDK-supported containment configurations. +Choose one of the SDK-defined `Containment` subclasses listed below. ```csharp public abstract class Containment @@ -1111,7 +1111,7 @@ public sealed class ProvisionOptions ## `Microsoft.Mxc.Sdk.V1.ProvisionRequest` -Closed request for provisioning a supported containment. +SDK-defined base type for backend-specific provision requests. ```csharp public abstract class ProvisionRequest @@ -1123,7 +1123,7 @@ public abstract class ProvisionRequest ## `Microsoft.Mxc.Sdk.V1.ProvisionMetadata` -Closed backend-specific metadata returned by provisioning. +SDK-defined base type for backend-specific metadata returned by provisioning. ```csharp public abstract class ProvisionMetadata diff --git a/docs/api-reference/node/v1/api.md b/docs/api-reference/node/v1/api.md index 9edd9204e..e60087d77 100644 --- a/docs/api-reference/node/v1/api.md +++ b/docs/api-reference/node/v1/api.md @@ -17,12 +17,12 @@ Callers do not supply JSON or a schema version. Creation takes `ContainerRequest` and operation options. Existing-container execution takes the `ContainerId` returned by provision, `ExecutionRequest`, -and operation options. PTY operations are asynchronous. One-shot PTY support +and operation options. PTY operations are asynchronous. Create-and-run PTY support covers IsolationSession, Bubblewrap, LXC, and Seatbelt direct execution. Existing-container PTY support remains IsolationSession-only. Seatbelt PTY rejects `guiAccess` and legacy `launchMethod: "open"`. Terminal handles give the caller explicit input, output, resize, and process -ownership; there is no separate attached-console or raw-JSON launch API. +ownership; there is no separate attached-console launch API or API accepting ["MXC request JSON"](../../../schema.md#mxc-request-json). ## Discovery and validation @@ -98,7 +98,7 @@ export function getUserProfilePolicy(environment?: { [key: string]: string | und ## `@microsoft/mxc-sdk/v1::mxcErrorFromCode` -Constructs an MxcError from a wire-format error code. +Constructs an `MxcError` from a native runtime error code. ```typescript export function mxcErrorFromCode(code: string, message: string, details?: Record): MxcError; @@ -116,7 +116,7 @@ export function probe(request?: ContainerRequest): ProbeOutput; ## `@microsoft/mxc-sdk/v1::provisionContainer` -Provision a container from a closed backend-specific request. +Provision a container from the backend-specific `ProvisionRequest` type. ```typescript export async function provisionContainer(request: ProvisionRequest & { diff --git a/docs/api-reference/node/v1/types.md b/docs/api-reference/node/v1/types.md index 83b11b3eb..3517465d2 100644 --- a/docs/api-reference/node/v1/types.md +++ b/docs/api-reference/node/v1/types.md @@ -148,7 +148,7 @@ export interface ContainerRequest { ## `@microsoft/mxc-sdk/v1::Containment` -Closed union and SDK-owned containment choices for container creation requests. +Union of the SDK-defined containment choices for `ContainerRequest`. ```typescript export type Containment = Containment.Process | Containment.ProcessContainer | Containment.Wslc | Containment.Lxc | Containment.Seatbelt | Containment.IsolationSession | Containment.Bubblewrap; @@ -237,7 +237,7 @@ export interface DirectionalNetworkConfig { ## `@microsoft/mxc-sdk/v1::ErrorCode` -Closed set of MXC wire-format error codes. +The SDK-defined set of MXC native runtime error codes. ```typescript export type ErrorCode = 'malformed_request' | 'unsupported_containment' | 'unsupported_phase' | 'backend_unavailable' | 'malformed_id' | 'stale_id' | 'not_provisioned' | 'not_started' | 'already_started' | 'already_stopped' | 'policy_validation' | 'backend_error'; @@ -343,7 +343,7 @@ export interface FilesystemPolicyResult { ## `@microsoft/mxc-sdk/v1::IsolationSessionNetworkConfig` -The only network posture IsolationSession can truthfully provide. +Unrestricted networking, the only network permissions IsolationSession supports. ```typescript export interface IsolationSessionNetworkConfig { @@ -407,7 +407,7 @@ export interface LxcConfig { ## `@microsoft/mxc-sdk/v1::MxcError` -Typed error thrown by the MXC SDK in response to a wire-format error envelope. +Typed error thrown by the MXC SDK in response to a native JSON error object. ```typescript export class MxcError extends Error { @@ -425,7 +425,7 @@ export class MxcError extends Error { ## `@microsoft/mxc-sdk/v1::MxcErrorFields` -Every field an MxcError can carry, in the same flat shape as the wire error envelope — operation, nativeCode and remediation sit alongside code and message, not nested inside details. +Every field an `MxcError` can carry, matching the native JSON error object: `operation`, `nativeCode`, and `remediation` sit alongside `code` and `message`, not nested inside `details`. ```typescript export interface MxcErrorFields { @@ -678,7 +678,7 @@ export interface ProvisionOptions { ## `@microsoft/mxc-sdk/v1::ProvisionRequest` -Closed backend-specific request for provisioning a container. +Backend-specific input for provisioning a container, selected by `containment`. ```typescript export type ProvisionRequest = C extends LifecycleContainmentKind ? ProvisionConfigFor & { @@ -735,7 +735,7 @@ export interface NetworkRuntimeConfig { ## `@microsoft/mxc-sdk/v1::SDK_CONTRACT_VERSION` -SDK-owned exact container-request contract used by the V1 high-level API. +The contract version selected internally by the V1 SDK API. ```typescript const SDK_CONTRACT_VERSION: "1.0.0" = '1.0.0' as const; diff --git a/docs/api-reference/rust/v1/api.md b/docs/api-reference/rust/v1/api.md index a92b92b7d..90b613703 100644 --- a/docs/api-reference/rust/v1/api.md +++ b/docs/api-reference/rust/v1/api.md @@ -19,7 +19,7 @@ Attached execution is not exposed by the V1 SDK. Creation takes `ContainerRequest` and operation options. Existing-container execution takes the `ContainerId` returned by provision, `ExecutionRequest`, -and operation options. One-shot PTY support covers IsolationSession, Bubblewrap, +and operation options. Create-and-run PTY support covers IsolationSession, Bubblewrap, LXC, and Seatbelt direct execution. Existing-container PTY support remains IsolationSession-only. Seatbelt PTY rejects `guiAccess` and legacy `launchMethod: "open"`. A PTY gives the diff --git a/docs/api-reference/rust/v1/types.md b/docs/api-reference/rust/v1/types.md index 56dafe324..2fd24faaf 100644 --- a/docs/api-reference/rust/v1/types.md +++ b/docs/api-reference/rust/v1/types.md @@ -143,7 +143,7 @@ impl ContainerRequest { ## `mxc_sdk::v1::Containment`, `mxc_sdk::v1::policy::Containment` -The closed backend choice carried by `ContainerRequest`. Select a variant with +The SDK-defined backend choices carried by `ContainerRequest`. Select a variant with its typed configuration; the native engine validates backend and policy support. ```rust @@ -200,7 +200,7 @@ impl From for Error { ## `mxc_sdk::v1::ErrorCode` -Closed set of error codes the SDK can return. +The predefined error codes the SDK can return. ```rust pub enum ErrorCode { diff --git a/docs/backends/bwrap/bubblewrap-backend.md b/docs/backends/bwrap/bubblewrap-backend.md index 02d9584e1..85c428b55 100644 --- a/docs/backends/bwrap/bubblewrap-backend.md +++ b/docs/backends/bwrap/bubblewrap-backend.md @@ -9,12 +9,12 @@ requiring root privileges or a container runtime. > **Status:** Stable — the default Linux backend. -> **Supported exact contracts (v0.9+):** author `network.egress` / +> **Supported contracts (v0.9+):** use `network.egress` / > `network.ingress` and, for proxy requests, `runtimeConfig.networkProxy`. > Versions before `0.9.0-alpha` are rejected; changing only the version of an > old config does not migrate its policy. Legacy `defaultPolicy`, > `enforcementMode`, host lists, `allowLocalNetwork`, and `network.proxy` -> are rejected by supported exact contracts. Typed Rust requests no longer +> are rejected by supported contracts. Typed Rust requests no longer > contain these fields. See [schema migration](../../schema.md). > Bubblewrap still rejects enforceable-looking requests it cannot honor > (such as `ingress.default: "allow"` or direct egress rules combined with a @@ -87,7 +87,7 @@ requiring root privileges or a container runtime. installed by an unprivileged supervisor that keeps the caller's uid, so on a stock host with root-owned `/run` it cannot take that lock. `validate` refuses such a host with a message naming the backend rather than letting - the supervisor die at the first rule. If pinned to `iptables-legacy`, switch it: + the supervisor die at the first rule. If configured to use `iptables-legacy`, switch it: ```bash sudo update-alternatives --set iptables /usr/sbin/iptables-nft sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-nft @@ -314,7 +314,7 @@ regardless of the rule. MXC emits a warning naming those allowed destinations rather than refusing them, since the posture fails closed. An IPv4-mapped address such as `::ffff:203.0.113.5` is programmed as IPv4: -Linux puts a genuine IPv4 packet on the wire for one, so an `ip6tables` rule +Linux sends a genuine IPv4 packet for one, so an `ip6tables` rule naming it would never match and a directional deny rule under `egress.default: "allow"` would fail open. A mapped CIDR is translated the same way — the mapped range is the last 32 bits of `::ffff:0:0/96`, so a @@ -408,8 +408,8 @@ added in the same change that enables it. Schema `0.9.0-alpha` uses an explicit `egress` and `ingress` section instead of the retired `defaultPolicy` / `allowedHosts` / `blockedHosts` fields. An -exact v0.9 config carrying a legacy field fails parsing; a config declaring -an earlier version is refused as an unsupported contract before its network +`0.9.0-alpha` config carrying a legacy field fails parsing; a config declaring +an earlier version is refused as an unsupported contract version before its network section is read. ```json @@ -510,7 +510,7 @@ is opened. A proxy config that states — or defaults to — `deny` therefore ge the posture it writes, since the deny still covers every host-loopback path but that endpoint. `ingress.hostLoopback` is not consulted there — `EgressPlan::for_proxy` builds that chain, not the directional builder that lowers the drop — but the -observed result matches the contract regardless. +observed result still matches the requested network policy. The declaration and these refusals must ship together — declaring the inbound features without them would be a fail-open. A unit test asserts exactly that @@ -530,7 +530,7 @@ is over-declared and fails there. Reverting the host-loopback drop reproduces the original bug as a test failure. `runtimeProxy` is declared. The parser normalizes -`runtimeConfig.networkProxy` into `policy.network_proxy`, pinned to a loopback +`runtimeConfig.networkProxy` into `policy.network_proxy`, restricted to a loopback endpoint and accepted only alongside `egress.default: "deny"` with no direct rules. That is the proxy-only posture this backend enforces. The end-to-end test runs the supported proxy configuration with ingress denial both omitted @@ -690,7 +690,7 @@ an exit is detected; see [Limitations](#limitations). A proxy request is the proxy-only posture, so `egress.default` must be `deny` with no `allow` / `deny` rules; the chain opens the proxy endpoint alone. -Exact contracts before v0.9 are retired. Legacy `network.proxy` and host-list +Contract versions before `0.9.0-alpha` are retired. Legacy `network.proxy` and host-list configurations cannot be expressed by declaring v0.9; use `runtimeConfig.networkProxy` with a ruleless, deny-default directional egress policy as shown above. The external proxy enforces any host filtering itself. @@ -803,7 +803,7 @@ blocks, not DNS names. Do not put a hostname in a `cidr` field: Bubblewrap cannot enforce an IP rule against the workload's changing DNS answers and rejects the request. If policy must inspect requested hostnames, use `runtimeConfig.networkProxy` with a separately configured external proxy. -The exact v0.9 contract provides no MXC-managed host-list policy for that proxy. +Contract `0.9.0-alpha` provides no MXC-managed host-list policy for that proxy. ## Comparison with LXC @@ -852,8 +852,8 @@ Test configs are in `tests/configs/bubblewrap_*.json`. IPv6 destinations remain unreachable until slirp supports IPv6 here. `runtimeConfig.networkProxy` restricts direct egress to the configured loopback proxy endpoint; hostname policy belongs to that external proxy. -- **No state-aware lifecycle** — Bubblewrap implements `ScriptRunner` only - (one-shot), not `StatefulSandboxBackend` +- **No container lifecycle operations**: Bubblewrap's `ScriptRunner` + supports create-and-run execution, not `StatefulSandboxBackend` lifecycle calls. - **Provider-loss detection is exit-based** — in the private-namespace modes a `slirp4netns` that exits mid-run fails the run; one that is alive but wedged is not detected, and reaches the workload as an unreachable network diff --git a/docs/backends/hyperlight/hyperlight-backend.md b/docs/backends/hyperlight/hyperlight-backend.md index f65e07499..337c2d0bf 100644 --- a/docs/backends/hyperlight/hyperlight-backend.md +++ b/docs/backends/hyperlight/hyperlight-backend.md @@ -81,7 +81,7 @@ three things: | Entry | Purpose | |---|---| -| `initrd.cpio` | The guest rootfs, pulled from `ghcr.io/hyperlight-dev/hyperlight-unikraft/` at the release the crate is pinned to | +| `initrd.cpio` | The guest rootfs, pulled from `ghcr.io/hyperlight-dev/hyperlight-unikraft/` at the release specified by the crate | | `snapshot/` | The guest, booted once and captured with the runtime warm; every run restores it | | `VERSION` | The rootfs release the directory holds | diff --git a/docs/backends/lxc/lxc-backend.md b/docs/backends/lxc/lxc-backend.md index d3ff17f04..e951c716c 100644 --- a/docs/backends/lxc/lxc-backend.md +++ b/docs/backends/lxc/lxc-backend.md @@ -4,7 +4,7 @@ The LXC backend provides Linux container isolation using [LXC (Linux Containers)](https://linuxcontainers.org/lxc/). -For every registered contract from `0.9.0-alpha` onward, networking is +For every supported contract from `0.9.0-alpha` onward, networking is directional-only: use `network.egress` and `network.ingress`. LXC rejects `runtimeConfig.networkProxy`, so a supported LXC request has no proxy surface at all — see [Proxy](#proxy) below. @@ -167,7 +167,7 @@ The field must name a loopback endpoint, and an LXC container has its own network namespace, so `127.0.0.1` there is the container rather than the host. MXC ships no component that relays a host loopback proxy into that namespace. -The legacy `network.proxy` fields also have no supported exact contract. +The legacy `network.proxy` fields are also absent from every supported contract. Choose a backend that supports a loopback proxy if the workload needs one; relabeling a retired LXC proxy request as v0.9 cannot make its policy enforceable. @@ -252,7 +252,7 @@ LXC reads a run's network section only when the container starts. Omit **Teardown is owed on every terminal path,** including a drop without `wait`: the network chains come down, and the container is released. A reused container -also has any stale proxy hosts-file pin from an earlier run cleared before its +also has any stale proxy address mapping in its hosts file cleared before its new workload launches. MXC checks for marked lines inside the running container using `lxc-attach` and removes them if present. A failed check refuses to run the workload. @@ -358,10 +358,10 @@ The zone query should answer the zone you assigned. Stock `lxcbr0` gives the container no IPv6 address, so this surfaces only on a host that provides one. - **No proxied egress.** See [Proxy](#proxy). -- **No state-aware lifecycle.** LXC implements `ScriptRunner` (one-shot) and +- **No container lifecycle operations.** LXC implements `ScriptRunner` (create-and-run execution) and `SandboxBackend` (streaming over pipes or a caller-controlled PTY), not `StatefulSandboxBackend`. A - state-aware request is rejected. + request for a container lifecycle operation is rejected. - **Inherited stdio is unavailable.** Use ordinary pipe streaming or `spawn_with_pty`; the in-process API never takes over the host application's own terminal. See [Streaming](#streaming). diff --git a/docs/backends/process-container/UIPolicy_Schema.md b/docs/backends/process-container/UIPolicy_Schema.md index 90b5ac182..34026a6bb 100644 --- a/docs/backends/process-container/UIPolicy_Schema.md +++ b/docs/backends/process-container/UIPolicy_Schema.md @@ -33,7 +33,7 @@ Default-deny is a strong security posture, but it raises a compatibility questio **Within a version**, default-deny applies fully — omitted fields are denied, and new fields introduced in a minor revision of the same version are denied for all existing configs. -**Across major versions**, the behavior is different: applications pinned to an older version continue to run under the policy semantics of that version. New fields introduced in a later major version are not applied to configs that declare an older version — preserving the behavior the developer explicitly designed for. This allows the schema to evolve without silently changing the security posture of deployed applications. +**Across major versions**, the behavior is different: applications using an older schema version continue to run under the policy semantics of that version. New fields introduced in a later major version are not applied to configs that declare an older version — preserving the behavior the developer explicitly designed for. This allows the schema to evolve without silently changing the security posture of deployed applications. This makes version a compatibility contract: a config that says `"version": 1` will always behave as a v1 config, regardless of what the current schema version is. diff --git a/docs/backends/process-container/networking.md b/docs/backends/process-container/networking.md index 4551443c2..6bc04a87d 100644 --- a/docs/backends/process-container/networking.md +++ b/docs/backends/process-container/networking.md @@ -2,7 +2,7 @@ > **Audience:** MXC consumers and developers -Supported exact contracts from `0.9.0-alpha` use the shared `network.egress` +Supported contracts from `0.9.0-alpha` use the shared `network.egress` and `network.ingress` policy plus `runtimeConfig.networkProxy` and `processContainer.network.allowedProxyPeer` configuration. @@ -285,8 +285,8 @@ durable DNS names or application payloads. Its ingress schema has only ## 2. Supported-contract selection and downlevel behavior Both WFP filter writes and per-container WinHTTP proxy configuration require a -privileged context. Supported exact contracts select the strongest -usable process-creation contract through runtime probing. +privileged context. For requests using supported contracts, MXC probes the +host and selects the strongest usable process-creation API. **Preferred selection:** Use PSEC (`CreateProcessSecurityEnvironment`) when its complete export set and runtime support probe succeed. PSEC is the only ProcessContainer path that receives directional egress filters, proxy peer identity, or diff --git a/docs/backends/seatbelt/seatbelt-backend.md b/docs/backends/seatbelt/seatbelt-backend.md index cf2552a15..cb35eb515 100644 --- a/docs/backends/seatbelt/seatbelt-backend.md +++ b/docs/backends/seatbelt/seatbelt-backend.md @@ -192,7 +192,7 @@ Anything it hasn't declared is rejected up front. ### Fields (supported schema 0.9+) -This is the cross-backend directional shape accepted by the registered exact +This is the cross-backend directional format accepted by the supported contracts. Schema 0.8 is no longer accepted; see the [supported network fields](../../schema.md#directional-networking-supported-contracts). @@ -638,7 +638,7 @@ deliberate: the alternative is a rule that matches nothing, which for |---|---| | `guiAccess: true` with `ui.disable: true`, or with no `ui` section | The GUI rules are only emitted when UI is enabled, so the request would otherwise be dropped without a word | | `guiAccess: true` with piped stdio or a caller-controlled PTY | GUI mode needs inherited stdio and an externally owned terminal | -| `launchMethod` in a supported contract | The field is retired and rejected at exact parsing | +| `launchMethod` in a supported contract | The field is retired and rejected during JSON parsing | ## Limitations diff --git a/docs/backends/windows-sandbox/windows-sandbox-reference.md b/docs/backends/windows-sandbox/windows-sandbox-reference.md index b94306c3a..245fc6ceb 100644 --- a/docs/backends/windows-sandbox/windows-sandbox-reference.md +++ b/docs/backends/windows-sandbox/windows-sandbox-reference.md @@ -16,7 +16,7 @@ The host opens four TCP connections on boot: | Stdout | Child standard output | | Stderr | Child standard error | -State-aware execution reconnects the three data channels after each +Execution through container lifecycle operations reconnects the three data channels after each `StreamsReady`; the control channel remains attached to the daemon. ### Authentication and channel roles @@ -50,7 +50,7 @@ Control messages then use a four-byte little-endian length followed by JSON. | `StreamsReady` | Guest to host | Data channels may reconnect | | `Ping` / `Pong` | Either | Liveness | -## State-Aware Daemon IPC +## Lifecycle Daemon Inter-Process Communication The daemon listens on an OS-assigned localhost port recorded in `daemon.json`. A phase process sends: @@ -73,8 +73,8 @@ The daemon restores or marks-unusable the guest slot and releases its mutex before writing the terminal exit frame. This makes terminal completion the point at which a new execution may be admitted. -The daemon record (`daemon.json`) also carries the IPC wire-protocol version the -daemon speaks, and the version is negotiated on the wire, not merely advertised. +The daemon record (`daemon.json`) also carries the version of its inter-process +communication protocol, and the client and daemon check that version during communication. The client sends its version as the trailing token of the `EXEC` line (`EXEC `), and the daemon refuses a mismatch with `ERR protocol …` **before** any frame exchange. Because the check is enforced by @@ -89,7 +89,7 @@ command so an orphaned VM can always be reclaimed. ## Host State -### One-shot +### Create-and-run execution ```text %TEMP%\wxc-wsb\oneshot\\ @@ -102,7 +102,7 @@ command so an orphaned VM can always be reclaimed. rendezvous.txt ``` -### State-aware +### Container lifecycle operations ```text %TEMP%\wxc-wsb\state-aware\ @@ -139,11 +139,11 @@ the bridge, and runs `C:\Sandbox-Rendezvous\bootstrap.cmd`. No host runtime, including Python, is implicitly discovered or mapped. -## One-Shot Launch +## Create-and-Run Launch 1. Reject an ambient Tokio runtime before side effects. 2. Validate policy and acquire the host VM mutex. -3. Reconcile stale one-shot markers and live VM processes. +3. Reconcile stale create-and-run markers and live VM processes. 4. Create secured run state and write the initial launcher marker. 5. Generate the nonce, bootstrap script, and `.wsb` file. 6. Launch `WindowsSandbox.exe`. @@ -152,7 +152,7 @@ No host runtime, including Python, is implicitly discovered or mapped. 9. Send one execution request and relay stdio. 10. Tear down the owned VM and clear the marker after confirmed exit. -## State-Aware Lifecycle +## Container Lifecycle Operations ### Provision @@ -177,7 +177,7 @@ The daemon: Start polls the record until readiness or timeout. A stale daemon or VM is reclaimed only when recorded process identities intersect the live set. If the -host's single VM slot is already held by another VM owner (a concurrent one-shot +host's single VM slot is already held by another VM owner (a concurrent create-and-run run or another live daemon), the daemon exits with a distinct busy code and start surfaces `backend_unavailable` rather than an opaque error. @@ -226,20 +226,20 @@ Mapped roots must be absolute existing directories. The backend rejects: A denied path outside all shares is already inaccessible because Windows Sandbox shares nothing by default. -State-aware filesystem policy is accepted only at provision and is immutable +Filesystem policy for container lifecycle operations is accepted only at provision and is immutable afterward. ### Network and UI -One-shot supports only default network policy `block`; `allow`, host filters, +Create-and-run execution supports only default network policy `block`; `allow`, host filters, and proxies are rejected. -State-aware phases reject network and UI policy. The guest firewall still +Container lifecycle operations reject network and UI policy. The guest firewall still enforces unconditional network lockdown. ## Teardown and Recovery -One-shot and state-aware modes share `Local\wxc-wsb-vm`, serialising ownership +Create-and-run execution and container lifecycle operations share `Local\wxc-wsb-vm`, serialising ownership of the host's single Windows Sandbox VM. Cleanup rules: @@ -279,17 +279,17 @@ These fields remain parseable but do not control either live execution path: - `windowsSandbox.idleTimeout` - `windowsSandbox.daemonPipeName` -State-aware lifecycle has no idle watchdog. +Container lifecycle operations have no idle watchdog. ## Debugging -Inspect one-shot state under: +Inspect create-and-run records under: ```powershell $root = Join-Path $env:TEMP "wxc-wsb\oneshot" ``` -Inspect state-aware records under: +Inspect lifecycle records under: ```powershell $root = Join-Path $env:TEMP "wxc-wsb\state-aware" @@ -311,17 +311,17 @@ PID-plus-creation-time ownership proof. | File | Purpose | |---|---| -| `src/mxc-sdk/src/core/mxc_engine/run.rs` | One-shot backend selection | -| `src/mxc-sdk/src/core/mxc_engine/state_aware.rs` | State-aware backend selection | -| `src/mxc-sdk/src/backends/windows_sandbox/lifecycle/one_shot.rs` | One-shot orchestration | -| `src/mxc-sdk/src/backends/windows_sandbox/lifecycle/state_aware.rs` | State-aware phase implementation | +| `src/mxc-sdk/src/core/mxc_engine/run.rs` | Backend selection for create-and-run execution | +| `src/mxc-sdk/src/core/mxc_engine/state_aware.rs` | Backend selection for container lifecycle operations | +| `src/mxc-sdk/src/backends/windows_sandbox/lifecycle/one_shot.rs` | Create-and-run orchestration | +| `src/mxc-sdk/src/backends/windows_sandbox/lifecycle/state_aware.rs` | Lifecycle operation implementation | | `src/mxc-sdk/src/backends/windows_sandbox/lifecycle/control_plane.rs` | Records, state decisions, IPC constants, and locks | -| `src/mxc-sdk/src/backends/windows_sandbox/lifecycle/teardown.rs` | One-shot markers and cleanup | +| `src/mxc-sdk/src/backends/windows_sandbox/lifecycle/teardown.rs` | Create-and-run markers and cleanup | | `src/mxc-sdk/src/backends/windows_sandbox/lifecycle/bridge.rs` | Guest bridge and stream relay | | `src/mxc-sdk/src/backends/windows_sandbox/lifecycle/ipc_exec.rs` | Daemon exec frame codec | | `src/mxc-sdk/src/backends/windows_sandbox/lifecycle/vm.rs` | VM generation, launch, proof, and teardown | | `src/mxc-sdk/src/backends/windows_sandbox/lifecycle/policy.rs` | Policy mapping and validation | -| `src/mxc-sdk/src/bin/windows_sandbox_daemon/main.rs` | State-aware daemon ownership and launch | +| `src/mxc-sdk/src/bin/windows_sandbox_daemon/main.rs` | Lifecycle daemon ownership and launch | | `src/mxc-sdk/src/bin/windows_sandbox_daemon/control_server.rs` | Daemon IPC and single-flight execution | | `src/mxc-sdk/src/backends/windows_sandbox/common/auth.rs` | Nonce and role authentication | | `src/mxc-sdk/src/backends/windows_sandbox/common/sandbox_protocol.rs` | Guest control framing | diff --git a/docs/backends/windows-sandbox/windows-sandbox.md b/docs/backends/windows-sandbox/windows-sandbox.md index ce2c0d562..1642bd197 100644 --- a/docs/backends/windows-sandbox/windows-sandbox.md +++ b/docs/backends/windows-sandbox/windows-sandbox.md @@ -8,13 +8,13 @@ The Windows Sandbox backend provides VM-level isolation using [Windows Sandbox](https://learn.microsoft.com/en-us/windows/security/application-security/application-isolation/windows-sandbox/windows-sandbox-overview). It is experimental and requires `--experimental`. -The backend has two execution surfaces: +The backend has two execution modes: -- **One-shot:** each invocation launches a fresh disposable VM, runs one +- **Create-and-run execution:** each invocation launches a fresh disposable VM, runs one command, and tears the VM down before returning. Teardown is best-effort, not kernel-guaranteed — a launcher hard-kill can leave a wedged orphan; see the reference doc's "Hard-kill orphans and `--force-reclaim`" section. -- **State-aware:** `provision`, `start`, repeated `exec`, `stop`, and +- **Container lifecycle operations:** `provision`, `start`, repeated `exec`, `stop`, and `deprovision` calls share one VM through a detached host daemon. ## Architecture @@ -25,8 +25,8 @@ wxc-exec.exe `-- mxc_engine | `-- windows_sandbox_lifecycle::WindowsSandboxRunner - |-- one-shot ScriptRunner - `-- state-aware StatefulSandboxBackend + |-- create-and-run ScriptRunner + `-- lifecycle StatefulSandboxBackend | `-- wxc-windows-sandbox-daemon.exe (owns the VM between phase processes) @@ -45,17 +45,17 @@ WindowsSandbox.exe | Component | Location | Purpose | |---|---|---| -| Execution engine module | `src/mxc-sdk/src/core/mxc_engine/` | Selects one-shot and state-aware backends | +| Execution engine module | `src/mxc-sdk/src/core/mxc_engine/` | Selects backends for create-and-run execution and container lifecycle operations | | Lifecycle module | `src/mxc-sdk/src/backends/windows_sandbox/lifecycle/` | Policy, launch, bridge, records, ownership, and teardown | -| Host daemon | `src/mxc-sdk/src/bin/windows_sandbox_daemon/` | State-aware VM and guest-connection owner | +| Host daemon | `src/mxc-sdk/src/bin/windows_sandbox_daemon/` | Owns the VM and guest connection between lifecycle calls | | Shared protocol module | `src/mxc-sdk/src/backends/windows_sandbox/common/` | Nonce authentication and control framing | | Guest agent | `src/mxc-sdk/src/bin/windows_sandbox_guest/` | Runs commands and bridges stdio inside the VM | -## One-Shot Execution +## Create-and-Run Execution 1. Validate policy and reject an existing Tokio runtime before side effects. 2. Acquire the per-session mutex for the host's single Windows Sandbox VM. -3. Reconcile any stale one-shot marker and provably-owned orphan. +3. Reconcile any stale create-and-run marker and provably-owned orphan. 4. Create secured per-run state, a launch nonce, bootstrap script, and `.wsb` configuration. 5. Launch `WindowsSandbox.exe` and record PID-plus-creation-time ownership proof. @@ -64,13 +64,13 @@ WindowsSandbox.exe 7. Execute one command and capture the exit status and output. 8. Tear down the owned VM and remove the marker after confirmed exit. -Every invocation follows the complete lifecycle; one-shot execution never +Every invocation follows the complete lifecycle; create-and-run execution never reuses a warm VM. -## State-Aware Lifecycle +## Container Lifecycle Operations -The state-aware surface keeps the VM alive across separate `wxc-exec ---operation` processes. Raw SDK/FFI requests carry `phase` and, after +The lifecycle API keeps the VM alive across separate `wxc-exec +--operation` processes. ["MXC request JSON"](../../schema.md#mxc-request-json) carries `phase` and, after provision, `sandboxId` in JSON. Direct executor calls omit those routing fields from JSON and pass them as `--operation` and `--container-id`. The backend is inferred from the `wsb:` sandbox ID after provision. @@ -88,17 +88,17 @@ sandbox ID, readiness, and VM ownership proof under `%TEMP%\wxc-wsb\state-aware`. It receives its authentication nonce over stdin, not the command line. -The host supports only one Windows Sandbox VM per logon session. One-shot and -state-aware owners share the same host-slot mutex. Orphan reclaim requires +The host supports only one Windows Sandbox VM per logon session. Create-and-run and +lifecycle VM owners share the same host-slot mutex. Orphan reclaim requires recorded PID-plus-creation-time proof intersecting the live process set; unproven VMs are treated as foreign and left untouched. -There is no idle watchdog. A started state-aware sandbox remains active until +There is no idle watchdog. A container started through lifecycle operations remains active until `stop` or `deprovision`. ## Configuration -### One-shot +### Create-and-run execution ```json { @@ -111,9 +111,9 @@ There is no idle watchdog. A started state-aware sandbox remains active until } ``` -### State-aware provision +### Provision -The raw SDK/FFI provision request is: +The provision "MXC request JSON" is: ```json { @@ -133,18 +133,18 @@ For a direct executor call, remove `phase` from that JSON and invoke: wxc-exec.exe config.json --operation provision ``` -Subsequent raw SDK/FFI phases carry the returned `sandboxId` in JSON. Direct +Subsequent lifecycle calls carry the returned `sandboxId` in "MXC request JSON". Direct executor calls remove `phase` and `sandboxId` from JSON and pass `--operation --container-id `. The legacy `windowsSandbox.idleTimeoutMs`, `idleTimeout`, and `daemonPipeName` fields remain parseable for schema compatibility but do not -affect either execution surface. One-shot emits a targeted warning only for +affect either execution mode. Create-and-run execution emits a targeted warning only for non-default values. ## Policy Support -### One-shot +### Create-and-run execution | Policy | Behaviour | |---|---| @@ -153,17 +153,17 @@ non-default values. | `filesystem.deniedPaths` | Accepted outside shares; rejected when overlapping a mapped share | | Omitted network policy | Guest firewall blocks external networking | | `network.egress` or `network.ingress` supplied (even empty or deny-only) | Rejected; omit both sections to use guest isolation | -| Retired `defaultPolicy` and host lists | Rejected by the exact contract | +| Retired `defaultPolicy` and host lists | Rejected by the version-specific JSON request type | | Network proxy | Rejected | Mapped paths must be absolute existing directories. Files, nested mapped roots, and conflicting read-only/read-write entries are rejected. -### State-aware +### Container lifecycle operations Filesystem policy is accepted only during `provision` and is immutable afterward. Later phases reject filesystem policy. Network and UI policy are not -accepted by state-aware phases; the guest still enforces its unconditional +accepted by container lifecycle operations; the guest still enforces its unconditional network lockdown. ## Security Model @@ -174,11 +174,11 @@ network lockdown. - **Role-based pairing:** sockets are paired by declared role, not TCP accept order. - **Network isolation:** only the established host connection is permitted. -- **Secured records:** nonce, rendezvous, marker, and state-aware directories +- **Secured records:** nonce, rendezvous, marker, and lifecycle record directories use owner-only ACLs and reject roots owned by another user. - **Ownership-scoped teardown:** PID-plus-creation-time proof prevents PID reuse or a foreign VM from being treated as owned. -- **Single-flight state-aware exec:** the daemon admits only one execution at a +- **One lifecycle execution at a time:** the daemon admits only one execution at a time and restores the guest slot before reporting terminal completion. Same-user processes remain within the backend's trust boundary. @@ -196,13 +196,13 @@ required. ## Known Limitations -1. One-shot execution pays the full VM cold-boot cost on every invocation. +1. Create-and-run execution pays the full VM boot cost on every invocation. 2. Only one Windows Sandbox VM can be active per logon session. -3. One-shot stdin is buffered and capped at 64 MiB. +3. Create-and-run stdin is buffered and capped at 64 MiB. 4. Filesystem sharing supports directories only. 5. Network access is block-only; allow-lists, block-lists, proxies, and unrestricted outbound access are unsupported. -6. State-aware lifecycle has no idle timeout. +6. Container lifecycle operations have no idle timeout. ## Testing diff --git a/docs/backends/wslc/wsl-container-getting-started.md b/docs/backends/wslc/wsl-container-getting-started.md index 084c40fcb..9cdfee1c1 100644 --- a/docs/backends/wslc/wsl-container-getting-started.md +++ b/docs/backends/wslc/wsl-container-getting-started.md @@ -175,8 +175,8 @@ for the full administrator reference, including deployment and verification. A single pull is bounded at 540 seconds; `MXC_WSLC_PULL_TIMEOUT_SECS` overrides it. -A state-aware pull is additionally held 60 seconds under the deadline its -client is waiting on, so that a slow registry surfaces as a failed provision +An image pull during `provision` must also finish at least 60 seconds before +the client's daemon-call deadline, so that a slow registry causes a failed provision rather than a timeout that abandons a container. Raising the budget past 540 seconds on that path therefore needs `MXC_WSLC_DAEMON_CALL_TIMEOUT_SECS` (default 600) raised with it. @@ -330,7 +330,7 @@ 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. -The state-aware lifecycle takes the same list under +The separate `provision` operation takes the same list under `wslc.provision.portMappings`, which requires schema `1.1.0-alpha`: ```json @@ -348,9 +348,9 @@ The state-aware lifecycle takes the same list under } ``` -The state-aware daemon creates one container per sandbox, so a forward belongs +The lifecycle daemon creates one container per sandbox, so a forward belongs to the sandbox that declared it. The session-wide `cpuCount` / `memoryMb` / -`gpu` / `storagePath` settings stay one-shot-only, because that daemon shares a +`gpu` / `storagePath` settings remain available only for create-and-run execution, because that daemon shares a single WSL session across every sandbox. ### Image sources @@ -399,8 +399,8 @@ no separate `--setup-wslc` step is required. > **No per-host filtering primitive exists.** The container lacks > `CAP_NET_ADMIN`; MXC refuses unsupported rules rather than running them -> unenforced. The removed legacy `allowOutbound` authoring and wire host-list -> vocabulary is not accepted by any supported exact contract. +> unenforced. The removed legacy `allowOutbound` setting and JSON host-list +> fields are not accepted by any supported contract. ### Network proxy (cooperative, unprivileged) @@ -447,9 +447,9 @@ proxy must have an address routable from the container: ``` The removed `network.proxy` object and its `localhost`/`builtinTestServer` -forms are rejected by exact v0.9. One-shot proxy use requires the unrestricted +forms are rejected by schema `0.9.0-alpha`. Create-and-run proxy use requires the unrestricted bridged posture shown above; a proxy cannot make isolated networking reach an -external listener. State-aware exec instead supplies only `runtimeConfig` and +external listener. Execution in an existing container (`exec`) instead supplies only `runtimeConfig` and inherits its provisioned network posture. ### Per-host filtering is not supported @@ -480,7 +480,7 @@ dispatch. In supported contracts, `network.enforcementMode`, and fail structural parsing; there is no legacy `"capabilities"` selector. Use the directional all-allow or all-deny posture above. Inbound reachability requires explicit host-to-container forwards through `wslc.portMappings` -(one-shot) or `wslc.provision.portMappings` (state-aware); +(create-and-run execution) or `wslc.provision.portMappings` (container lifecycle operations); `ingress.default: "allow"` alone does not create them. **Caveats** @@ -504,14 +504,14 @@ the container. ### Working directory (`process.cwd`) -One-shot runs take `process.cwd` as a local Windows drive path and map it the +Create-and-run execution takes `process.cwd` as a local Windows drive path and maps it the same way: `C:\workspace` starts the process in `/mnt/c/workspace`. Grant the directory in `filesystem` so it is mounted. A value that cannot be mapped — a relative, drive-relative (`C:work`), UNC, or in-container path such as `/workspace` — is **rejected** before the container is created. An omitted or blank `cwd` leaves the container's default working directory in place. -State-aware `exec` takes the opposite form: an absolute in-container path such +Execution in an existing container (`exec`) takes the opposite form: an absolute in-container path such as `/work`. ### Environment @@ -582,16 +582,16 @@ you can write through unenforced. Omit the section entirely. `WSLC_CONTAINER_FLAG_AUTO_REMOVE`, and teardown stops and deletes the container. `lifecycle.destroyOnExit: false` is **rejected**. It asks for the container to -outlive the run, which a one-shot invocation cannot deliver: the container is +outlive the run, which create-and-run execution cannot deliver on this backend: the container is scoped to a session this process owns, terminating that session at the end of the run reaps the container regardless of the AutoRemove flag, and the WSLC SDK -has no cross-process re-attach. Use the state-aware lifecycle if you need a +has no cross-process re-attach. Use manual lifecycle operations if you need a container to persist — its daemon holds the session open across phase processes. `lifecycle.preservePolicy: true` is **rejected** — WSLC has no policy-persistence primitive, so there is nothing for the flag to select. -Note the state-aware surface differs: it rejects the whole `lifecycle` section +Container lifecycle JSON requests differ: they reject the whole `lifecycle` section at parse time, because a multi-invocation sandbox's lifetime is driven by the explicit `provision` / `deprovision` phases rather than by per-run flags. @@ -632,11 +632,11 @@ images — cannot be used. | HRESULT `0x80040327` (`WSL_E_OS_NOT_SUPPORTED`) from any WSLC call | The SDK reached WSL's service-connect guard on a host that is neither Windows 11 nor has the WSL support interface | Upgrade Windows. `0x80040321` (`WSL_E_WSL_OPTIONAL_COMPONENT_REQUIRED`) is the sibling code when the legacy `lxss` component is absent too | | `Failed to load wslcsdk.dll` | DLL not in same directory as `wxc-exec.exe` | Copy `wslcsdk.dll` next to the binary | | `WSLC runtime unavailable` | WSL runtime package is missing, older than 2.9.9, or the Virtual Machine Platform optional component is disabled | Update WSL with `wsl --update --pre-release`, verify the installed version with `wsl --version`, and enable the Virtual Machine Platform optional component if required. The WSLC SDK DLL is a separate dependency and does not replace the WSL runtime package. | -| `WSLC runtime unavailable. Missing components: SdkNeedsUpdate` | The opposite direction: your installed WSL is **newer** than the WSLc SDK this MXC build ships (pinned by `WSLC_SDK_VERSION` in `src/mxc-sdk/build/build_wslc_common.rs`) | Update MXC to a build with a newer pinned SDK. Do **not** update WSL — it is already ahead, and updating it further will not clear this. | +| `WSLC runtime unavailable. Missing components: SdkNeedsUpdate` | The opposite direction: your installed WSL is **newer** than the WSLc SDK this MXC build ships (specified by `WSLC_SDK_VERSION` in `src/mxc-sdk/build/build_wslc_common.rs`) | Update MXC to a build that includes a newer SDK. Do **not** update WSL — it is already ahead, and updating it further will not clear this. | | `WSLC image '' is not cached, and this sandbox declares no egress` | An isolated config named an image the store does not have | Warm the cache with `--setup-wslc`, set `imageTarPath`, or allow egress | | `WSLC image '' cannot be pulled: '' is not in the administrative registry allowlist` | Machine policy restricts which registries may be used | Use a permitted registry, set `imageTarPath`, or ask an administrator to widen `WslcAllowedImageRegistries` | | `WSLC image '' did not finish pulling within s and was stopped` | The pull exceeded its budget and was aborted | Retry, raise `MXC_WSLC_PULL_TIMEOUT_SECS`, or warm the cache from a faster network | -| `WSLC image '' did not finish pulling within s. The transfer was abandoned` | The registry stopped responding, so the pull was given up on rather than ended | Retry, or warm the cache from a faster network. On a state-aware run raise `MXC_WSLC_DAEMON_CALL_TIMEOUT_SECS` alongside `MXC_WSLC_PULL_TIMEOUT_SECS`, since the budget is held under it | +| `WSLC image '' did not finish pulling within s. The transfer was abandoned` | The registry stopped responding, so the pull was given up on rather than ended | Retry, or warm the cache from a faster network. For a separate `provision` operation, raise `MXC_WSLC_DAEMON_CALL_TIMEOUT_SECS` alongside `MXC_WSLC_PULL_TIMEOUT_SECS`, since the pull must finish before the daemon-call deadline | | `WSLC image '' could not be pulled` with `repository does not exist or may require 'docker login'` | The reference is wrong, or the registry needs credentials MXC cannot supply | Fix the image name and tag. For a private registry, use `imageTarPath` or import the image out of band | | `WSLC image '' could not be pulled` with `no such host` or a connection error | This host cannot reach the registry | Restore network access, or warm the cache from a connected machine with `--setup-wslc` and match `storagePath`. `imageTarPath` removes the dependency entirely | | `WSLC image '' could not be pulled` with `HRESULT 0x8004060D` | Administrative policy on the host blocks the registry | Use a permitted registry, or supply the image with `imageTarPath` | diff --git a/docs/backends/wslc/wslc-state-aware.md b/docs/backends/wslc/wslc-state-aware.md index ed975fb3b..6358056b4 100644 --- a/docs/backends/wslc/wslc-state-aware.md +++ b/docs/backends/wslc/wslc-state-aware.md @@ -1,20 +1,20 @@ -# WSLc State-Aware Lifecycle +# WSLc Container Lifecycle Operations > **Audience:** MXC consumers and developers -This document describes the **state-aware lifecycle** for the WSL Container (WSLc) backend: -the multi-invocation `provision → start → exec → stop → deprovision` surface that keeps a -container **warm** across separate `wxc-exec` phase processes, amortizing WSLc's high cold-start -cost. +This document describes **container lifecycle operations** for the WSL Container (WSLc) backend: +`provision → start → exec → stop → deprovision` calls that keep a +container running across separate `wxc-exec` phase processes, avoiding repeated +WSLc startup costs. See the [consumer glossary](../../glossary.md) for terminology. It complements: - [WSLC SDK bindings runbook](../../development/build-and-test/wslc-sdk-bindings.md) — regenerating the SDK bindings. -- [Container lifecycle architecture](../../development/architecture/container-lifecycle.md) — the cross-backend state-aware wire format, the Rust `StatefulSandboxBackend` trait, and the dispatcher contract. +- [Container lifecycle architecture](../../development/architecture/container-lifecycle.md) — the shared lifecycle JSON format, the Rust `StatefulSandboxBackend` trait, and backend routing. -The raw WSLc state-aware surface is available in published exact schemas +WSLc lifecycle ["MXC request JSON"](../../schema.md#mxc-request-json) is supported by published contracts beginning with `0.9.0-alpha`. The Rust, .NET, and Node high-level v1 lifecycle -APIs are V1 contract-mapped and emit stable exact `1.0.0`; callers do not +APIs use stable contract `1.0.0`; callers do not supply a schema version. Neither path requires a runtime experimental opt-in. Native builds still require the `wslc` feature (`build.bat --with-wslc`). @@ -23,7 +23,7 @@ Native builds still require the `wslc` feature (`build.bat --with-wslc`). The WSLc SDK (`wslcsdk.dll`, 2.9.9) has **no cross-process session re-attach**: every operation (`WslcCreateContainer`, `WslcStartContainer`, `WslcCreateContainerProcess`, image pull, stop / delete) requires a live in-process `WslcSession` / `WslcContainer` handle, and there is no -`WslcOpenSession`. State-aware runs each phase as a **separate** +`WslcOpenSession`. Each lifecycle phase runs as a **separate** `wxc-exec` invocation, so handles minted during `provision` cannot be reused by a later `exec` or `deprovision` in a different process. @@ -52,25 +52,25 @@ waits for the run, because deleting the container would free a handle the run is | Component | Location | Role | |-----------|----------|------| -| State-aware backend | `src/mxc-sdk/src/backends/wslc/common/state_aware.rs` (`WslcStateAwareRunner`) | Translates runtime `WslcProvisionConfig` and cross-cutting policy into daemon protocol frames; implements `StatefulSandboxBackend` (`ID_PREFIX`/`BACKEND_KEY` = `wslc`). | -| Policy honor matrix | `src/mxc-sdk/src/backends/wslc/common/policy.rs` | Per-phase validation of which policy fields are honored vs rejected. | +| Lifecycle backend | `src/mxc-sdk/src/backends/wslc/common/state_aware.rs` (`WslcStateAwareRunner`) | Translates runtime `WslcProvisionConfig` and shared policy fields into daemon protocol frames; implements `StatefulSandboxBackend` (`ID_PREFIX`/`BACKEND_KEY` = `wslc`). | +| Policy support checks | `src/mxc-sdk/src/backends/wslc/common/policy.rs` | Per-phase validation of which policy fields are enforced or rejected. | | Daemon client | `src/mxc-sdk/src/backends/wslc/common/daemon_client.rs` | Discovers / spawns the daemon, connects the control pipe, sends `DaemonRequest` frames, reads responses; typed `DaemonError`. | | Daemon | `src/mxc-sdk/src/bin/wslc_daemon/` (`wxc-wslc-daemon.exe`) | Long-lived host process holding `WslcSession` / `WslcContainer`; worker thread drives the SDK; idle-timeout watchdog tears the session down when unused. | -| Engine arm | `src/mxc-sdk/src/core/mxc_engine/state_aware.rs` | Dispatches the WSLc state-aware backend (Windows + `wslc` feature). | +| Engine routing | `src/mxc-sdk/src/core/mxc_engine/state_aware.rs` | Selects the WSLc lifecycle backend (Windows + `wslc` feature). | | Prefix registration | `src/mxc-sdk/src/tools/mxc_common/state_aware_dispatch.rs` (`backend_from_prefix`) | Maps the `wslc:` id prefix back to the WSLc backend for post-provision phases. | -Exact adapters construct `mxc_common::models::WslcProvisionConfig` directly from -`wslc.provision`. Engine-side checked binding preserves an absent +Version-specific adapters construct `mxc_common::models::WslcProvisionConfig` directly from +`wslc.provision`. Conversion to the backend's typed configuration preserves an absent config, a present empty config, and supplied `image`/`imageTarPath`/`portMappings` values without reparsing JSON. An omitted -image remains `None` until the backend chooses its default. The exact contract -type is converted once by its adapter; the backend's provision associated type -is the runtime-owned model, not a wire deserialization DTO. +image remains `None` until the backend chooses its default. Each version's JSON +request type is converted once by its adapter; the backend uses the runtime +`WslcProvisionConfig` model rather than deserializing JSON again. ### Port mappings `wslc.provision.portMappings` forwards host ports into the sandbox's container, -using the same entry shape as the one-shot `wslc.portMappings` list: +using the same entry shape as the create-and-run `wslc.portMappings` list: ```json { @@ -95,17 +95,17 @@ Port mappings require bridged networking. A request that leaves `network` out, or sets it to the all-`deny` isolated posture, is rejected at provision: the container has no networking for a forward to reach. -The field requires development contract `1.1.0-alpha`. State-aware WSLC -requests default to `0.9.0-alpha`, so the version has to be set explicitly. +The field requires development contract `1.1.0-alpha`. Requests declaring +`0.9.0-alpha` cannot include it and must select `1.1.0-alpha` explicitly. The daemon shares one session (VM) but creates a separate container for each provision, so a mapping applies only to the container that declared it, unlike the session-wide `cpuCount` / `memoryMb` / `gpu` / `storagePath` knobs that -remain one-shot-only. +remain available only for create-and-run execution. -The one-shot `wslc.portMappings` list and `wslc.provision.portMappings` share +The create-and-run `wslc.portMappings` list and `wslc.provision.portMappings` share one duplicate check, so two entries claiming the same `windowsPort` are rejected identically. Zero ports and non-TCP protocols are rejected when the -exact contract deserializes. +version-specific JSON request is deserialized. The forward listens on `127.0.0.1` only, so a mapped port reaches the container from the host itself and not from other machines. WSLC installs it when the @@ -115,7 +115,7 @@ process already holds on loopback fails the `start` phase, not `provision`. ## Sandbox IDs `provision` mints an id of the form `wslc:<32 lowercase hex>` (`wslc:` + a UUID simple form). -Raw SDK/FFI requests carry this id in `sandboxId` for every post-provision phase +"MXC request JSON" carries this id in `sandboxId` for every post-provision phase (`start` / `exec` / `stop` / `deprovision`). Direct `wxc-exec` calls omit it from JSON and pass it as `--container-id`; the dispatcher derives the backend from the `wslc:` prefix (later operations do **not** repeat `containment`). @@ -132,10 +132,10 @@ as `--container-id`; the dispatcher derives the backend from the `wslc:` prefix ### exec output semantics -`provision` / `start` / `stop` / `deprovision` return a JSON `{result | error}` envelope on stdout. +`provision` / `start` / `stop` / `deprovision` return a JSON object containing `result` or `error` on stdout. For `wxc-exec` CLI execution, a **successful** `exec` relays the script's raw stdout/stderr live from daemon frames and exits with the script's own exit code — it does **not** wrap the result in -an envelope. A CLI dispatch **failure** writes its `{error}` envelope to stderr, because the +a JSON result object. A CLI dispatch **failure** writes its `{error}` JSON object to stderr, because the script's output may already own stdout by the time the failure is known. In-process SDK execution uses the supported streaming APIs, which return separate stdout/stderr pipes; no stdin pipe is returned. Rust and .NET SDKs do not expose attached exec as a public operation. A timeout on the @@ -201,12 +201,12 @@ daemon attempts immediate deletion. If deletion fails, the quarantined entry is retained so `deprovision` can retry cleanup; it is not removed from tracking while a potentially running workload remains. -## Policy honor matrix +## Supported and rejected policy fields WSLc networking is **all-or-nothing** (`WslcContainerNetworkingMode` `None` vs `Bridged`); there is no per-host filtering or independent ingress/host-loopback restriction primitive. Proxy configuration supplies environment variables, not -a firewall. Exact v0.9 supports two coherent postures: +a firewall. Schema `0.9.0-alpha` supports two combinations: | Posture | `egress.default` | `ingress.default` | `ingress.hostLoopback` | | --- | --- | --- | --- | @@ -232,14 +232,14 @@ acknowledges that WSLC cannot independently restrict those directions. | `process.timeout` | n/a | n/a | honored → `ExecConfig.timeout_ms` | | `lifecycle` | rejected (whole section, at parse) | rejected | rejected | -For a raw `0.9.0-alpha` request, that exact request root is selected before -backend dispatch. High-level v1 SDK calls select exact `1.0.0` internally. -Fields absent from that phase's closed root fail structurally with +For "MXC request JSON" declaring `0.9.0-alpha`, that version's request type is selected before +backend execution. High-level v1 SDK calls select contract `1.0.0` internally. +Fields not declared for that phase are rejected during parsing with `malformed_request`: provision excludes `ui`, start / stop / deprovision admit no filesystem, network, UI, or process policy, and exec excludes filesystem and UI. These failures do not reach WSLc's presence checks. -Fields the exact root does admit still receive backend semantic validation. +Fields allowed by that phase's JSON request type still receive backend validation. Every resulting `policy_validation` above aborts the phase before anything is created: the dispatcher runs each `validate_*` hook ahead of the phase body, and `connect_daemon()` lives inside `provision()`, so a refused provision never @@ -261,11 +261,11 @@ directional requirements do not change those published contracts. ## Error mapping -Exact-contract `malformed_request` failures occur before daemon connection and +JSON parsing failures reported as `malformed_request` occur before daemon connection and are not part of daemon error mapping. Once dispatch reaches the backend, -`state_aware.rs::map_daemon_error` maps daemon errors to the cross-backend wire error codes: +`state_aware.rs::map_daemon_error` maps daemon errors to the shared SDK error codes: -| Source | Wire error code | +| Source | SDK error code | |--------|-----------------| | daemon `ErrKind::NotProvisioned` (incl. stale / deprovisioned id) | `not_provisioned` | | daemon `ErrKind::NotStarted` | `not_started` | @@ -276,8 +276,8 @@ are not part of daemon error mapping. Once dispatch reaches the backend, | WSLc feature absent / host cannot run WSLc | `backend_unavailable` | The daemon classifies each failure from the `failure_phase` its step helper -reported, so a phase failure reaches the SDK with the same code the one-shot -surface returns for the same host condition. A caller branches on the code +reported, so a phase failure reaches the SDK with the same code that +create-and-run execution returns for the same host condition. A caller branches on the code without matching the message. `ErrKind` is additive: a kind a client does not recognize decodes to @@ -297,7 +297,7 @@ caller sets these before the first `provision`: | `MXC_WSLC_DAEMON_IDLE_TIMEOUT_SECS` | `300` | Idle duration after which the daemon tears down the session. | | `MXC_WSLC_DAEMON_IDLE_POLL_SECS` | `15` | How often the idle watchdog checks for inactivity. | -The state-aware E2E harness (`tests/scripts/run_wslc_state_aware_tests.ps1`) uses short overrides so +The lifecycle end-to-end test script (`tests/scripts/run_wslc_state_aware_tests.ps1`) uses short overrides so it can observe idle-teardown within seconds. ## Testing @@ -316,7 +316,7 @@ section drives one phase process at a time. ### Running the fixtures (ordering + id substitution) -The `wslc_state_aware_*.json` fixtures are **stateful** — unlike the one-shot configs, they cannot be +The `wslc_state_aware_*.json` test inputs describe **container lifecycle operations**: unlike create-and-run configs, they cannot be run individually or in an arbitrary order: - **Order is mandatory.** A sandbox must go through `provision → start → exec… → stop → deprovision`. @@ -325,8 +325,8 @@ run individually or in an arbitrary order: `deprovision` → `not_provisioned`). - **The id must be threaded through.** `provision` returns the real `wslc:<32-hex>` id on stdout (`result.sandboxId`). The post-provision fixtures (`_start`, `_stop`, `_deprovision`, and every - `_exec_*`) ship with a literal **`{{SANDBOX_ID}}` placeholder**. These fixtures use the raw exact - SDK/FFI envelope shape; the harness replaces the placeholder before converting the request to + `_exec_*`) ship with a literal **`{{SANDBOX_ID}}` placeholder**. These test inputs use + "MXC request JSON"; the harness replaces the placeholder before converting the request to direct executor CLI form. `run_wslc_state_aware_tests.ps1` handles both concerns automatically (it drives the phases in order @@ -341,13 +341,13 @@ fixtures **through the harness**, not by pointing `wxc-exec --config` at them di infer that work on one sandbox completed because work on another did. - **`busy` collapses to `backend_error` (deferred).** A refused exec reaches an SDK caller as a - generic `backend_error` with no indication that retrying would succeed. A retryable wire code - needs a new `MxcErrorCode` variant, which is a closed set matching the SDK `ErrorCode` union + generic `backend_error` with no indication that retrying would succeed. A retryable error code + needs a new `MxcErrorCode` variant, whose predefined values match the SDK `ErrorCode` union one-for-one, so it spans Rust, Node, .NET, the versioned references, and schema regeneration. This is tracked as follow-up work. - **No typed SDK can set port mappings yet.** The Rust, Node, and .NET v1 SDKs - all pin the published stable contract `1.0.0`, which does not declare the - field, and released schemas are immutable. Raw `1.1.0-alpha` JSON through the + all use published stable contract `1.0.0`, which does not declare the + field, and released schemas are immutable. "MXC request JSON" using contract `1.1.0-alpha` through the FFI or `wxc-exec` is the only way to reach it today. Promoting `1.1.0-alpha` to a stable release is what lets the three SDKs expose it together. diff --git a/docs/container-lifecycle.md b/docs/container-lifecycle.md index c8d7a321a..360617d6d 100644 --- a/docs/container-lifecycle.md +++ b/docs/container-lifecycle.md @@ -2,15 +2,16 @@ > **Audience:** MXC consumers -Companion to the [full lifecycle and wire contract](development/architecture/container-lifecycle.md). +Companion to the [lifecycle architecture and native JSON format](development/architecture/container-lifecycle.md). +See the [consumer glossary](glossary.md) for terminology. MXC separates container creation from persistent lifecycle operations. -Creation runs a `ContainerRequest`; persistent execution provisions a -container, starts it, runs `ExecutionRequest` workloads, then stops and -deprovisions it. The public SDK surface is versioned independently of the -native wire contract. +Creation runs a `ContainerRequest`. With manual lifecycle operations, the caller +provisions a container, starts it, runs `ExecutionRequest` workloads, then stops +and deprovisions it. The public SDK API is versioned independently of the +native JSON request and response format. -## Public SDK surface +## Public SDK API All public types, operations, probes, backend/platform discovery, telemetry, and helpers live under `mxc_sdk::v1`, `Microsoft.Mxc.Sdk.V1`, or @@ -28,7 +29,7 @@ and helpers live under `mxc_sdk::v1`, `Microsoft.Mxc.Sdk.V1`, or | Stop | `container::stop_container` | `MxcLifecycle.StopContainer` | `stopContainer` | | Deprovision | `container::deprovision_container` | `MxcLifecycle.DeprovisionContainer` | `deprovisionContainer` | -- Creation takes a request followed by its operation-specific options. +- Creation takes `ContainerRequest` followed by its operation-specific options. - Provision takes `ProvisionRequest` followed by `ProvisionOptions`. - Start, stop, and deprovision take `ContainerId` followed by their own options. - Existing-container execution takes `ContainerId`, `ExecutionRequest`, and @@ -37,7 +38,7 @@ and helpers live under `mxc_sdk::v1`, `Microsoft.Mxc.Sdk.V1`, or Common process settings are command, working directory, environment, inherit-default-environment, and timeout. SDKs use properties or setters -according to their language conventions. Containment choices are closed: +according to their language conventions. Supported containment choices are defined by Rust enum variants, SDK-owned .NET subclasses, and Node discriminated unions. Creation defaults to generic `Process` intent. @@ -80,7 +81,7 @@ author typed SDK requests. ## References -- [Full lifecycle and wire contract](development/architecture/container-lifecycle.md) +- [Lifecycle architecture and native JSON format](development/architecture/container-lifecycle.md) - [Rust SDK](../src/mxc-sdk/README.md) - [.NET SDK](../sdk/dotnet/README.md) - [Node SDK](../sdk/node/README.md) diff --git a/docs/development/README.md b/docs/development/README.md index 802a8ff57..2fb7b7d05 100644 --- a/docs/development/README.md +++ b/docs/development/README.md @@ -6,6 +6,9 @@ This section covers MXC architecture, implementation, validation, and planned work. Consumer documentation remains under `docs/`; start with the [project README](../../README.md). +See the [developer glossary and wording guide](glossary.md) for implementation +terms, plain-English alternatives, and guidance on naming specific types. + ## Architecture - [Repository architecture](architecture/repository-architecture.md) diff --git a/docs/development/architecture/container-lifecycle.md b/docs/development/architecture/container-lifecycle.md index 110229bd1..76344f44a 100644 --- a/docs/development/architecture/container-lifecycle.md +++ b/docs/development/architecture/container-lifecycle.md @@ -2,6 +2,9 @@ > **Audience:** MXC developers +See the [developer glossary](../glossary.md) for existing implementation names +such as "state-aware," "one-shot," and "wire contract." + *Detailed design proposal. Compiled 2026-04-28.* ## Contents @@ -247,7 +250,7 @@ wire format and have different roles: | `containerId` | One-shot wire envelope (per `docs/schema.md`) | Caller-supplied (or auto-generated random hex) | Human-readable label, used as e.g. AppContainer profile name | For direct `wxc-exec` lifecycle calls, `--container-id` supplies the opaque -lifecycle routing identifier represented as `sandboxId` in raw JSON. It is +lifecycle routing identifier represented as `sandboxId` in ["MXC request JSON"](../../schema.md#mxc-request-json). It is distinct from the one-shot JSON `containerId` label: the CLI option is required for `start`, `exec`, `stop`, and `deprovision`, and is not accepted for `provision`. This is a CLI transport name only; the JSON field and native ABI @@ -367,8 +370,9 @@ consumer API and the backend guides for policy requirements. ## 7. Wire contract -The raw exact wire contract is a typed, JSON-serialised envelope shared by the -TypeScript SDK and `mxc_ffi`. Direct executor calls carry lifecycle routing in CLI +The ["MXC request JSON"](../../schema.md#mxc-request-json) contract defines +the JSON input to `mxc_ffi`, also produced internally by Node and .NET. +Typed SDK callers do not author JSON. Direct executor calls carry lifecycle routing in CLI arguments while retaining the same phase-specific exact contracts internally. Rust normalizes both paths into the same request types (§9.1). The only open content is at the leaves of `ErrorEnvelope.details`; every other field, including the error @@ -454,7 +458,7 @@ State-aware-only fields: #### `wxc-exec` lifecycle transport -Raw exact SDK and FFI calls retain `phase` and `sandboxId` in JSON. Direct +"MXC request JSON" retains `phase` and `sandboxId`. Direct `wxc-exec` lifecycle calls instead remove those routing fields from the supplied JSON and pass them as command-line arguments: @@ -525,8 +529,8 @@ reachable only through raw `1.1.0-alpha` JSON. | Innermost value | Backend-specific fields only (no cross-cutting, no `version`) | The SDK extracts these from the consumer's per-(backend, phase) Config | Compile-time enforcement of valid combinations lives on the SDK's per-(backend, phase) -Configs (§6.1), not on this illustrative aggregate. Raw-JSON callers writing -backend sections directly are validated by the exact Rust contract and +Configs (§6.1), not on this illustrative aggregate. "MXC request JSON" containing +backend sections is validated by the exact Rust contract and `validate_` hooks at runtime (§10.1). Runtime experimental authorization is supplied separately through `SandboxSpawnOptions.experimental` or the executor's `--experimental` flag; it is not a request JSON field. @@ -978,7 +982,7 @@ normalized request has no `source_contract` because no external exact contract produced it. Raw callers follow the separate exact path: ```text -raw JSON +"MXC request JSON" -> exact registered request root -> exact adapter -> CommonRequestIR + StateAwareOperation @@ -1063,8 +1067,8 @@ settings stay in the common request. Absent provision configuration remains `None`; an empty provision object remains a present config with absent fields; explicit empty strings remain supplied. Equivalent absent/empty outer wrappers need not survive. Backend validation and -defaulting retain ownership of those values. Successful requests retain no raw -backend JSON or source text. The bundling does not modify `ExecutionRequest`'s shape. Domain models +defaulting retain ownership of those values. Successful requests retain no +backend configuration JSON or source text. The bundling does not modify `ExecutionRequest`'s shape. Domain models are exposed to the dispatch layer; the wire types are an implementation detail of the parser and schema generation. @@ -1454,7 +1458,7 @@ fn dispatch_state_aware( `dispatch_state_aware_typed` returns `TypedDispatchOutcome` without serializing backend metadata. The high-level Rust SDK maps that result into `ProvisionResult`, `LifecycleResult`, or `ValidationResult` and typed backend -metadata. The raw JSON lane wraps the same typed dispatch result in +metadata. The path accepting "MXC request JSON" wraps the same typed dispatch result in `DispatchOutcome::Envelope` and serializes it for the wire response. JSON response construction is therefore confined to raw/executor entry points rather than being an implementation step of typed Rust calls. @@ -1631,7 +1635,7 @@ unconditionally by the in-guest agent). cannot accidentally pass them. - **Runtime enforcement at Rust.** The exact phase contract structurally rejects fields that are not representable for that phase, including input - from raw-JSON callers or a future SDK whose typing has drifted. Those failures + from callers providing "MXC request JSON" or a future SDK whose typing has drifted. Those failures surface as `malformed_request`. Backend `validate_` hooks then reject unsupported values or combinations among fields the exact contract admits; those failures surface as `policy_validation` (§8). Together these checks are diff --git a/docs/development/architecture/repository-architecture.md b/docs/development/architecture/repository-architecture.md index b76f47b5c..ec655fd43 100644 --- a/docs/development/architecture/repository-architecture.md +++ b/docs/development/architecture/repository-architecture.md @@ -93,7 +93,7 @@ operation. See the |---------|-------------| | Run-to-completion | Starts a workload, waits, and returns its outcome and output | | Streaming | Returns a live process handle with streams, wait, and kill operations | -| State-aware lifecycle | Uses separate provision, start, exec, stop, and deprovision calls | +| State-aware lifecycle | Manual lifecycle operations: provision, start, exec, stop, and deprovision | The common traits are defined in `mxc_sdk::mxc_common`; `mxc_sdk::mxc_engine` dispatches each surface to the selected backend. diff --git a/docs/development/architecture/versioning.md b/docs/development/architecture/versioning.md index c86558c2c..9948187fd 100644 --- a/docs/development/architecture/versioning.md +++ b/docs/development/architecture/versioning.md @@ -2,6 +2,9 @@ > **Audience:** MXC developers +See the [developer glossary](../glossary.md) for terms such as "exact contract," +"wire format," and "request root." + ## Architecture at a glance Versioning determines **which configuration contract is accepted**, not which @@ -12,16 +15,16 @@ separate: | --- | --- | | Which fields and values exist? | The exact Rust contract types and registration in `mxc_contract`. Published contracts are immutable; the development contract can evolve. | | Which exact contract does a typed SDK emit? | `sdkMajorTargets` in `schemas/schema-version.json`, checked against the exact Rust registry. Callers select a major-version SDK API, not an exact JSON version. | -| Which exact contract does raw JSON use? | The caller's declared, registered `version`. A version range or development opt-in cannot authorize another spelling. | +| Which contract does ["MXC request JSON"](../../schema.md#mxc-request-json) use? | The caller's declared, registered `version`. A version range or development opt-in cannot authorize another spelling. | | Which backend and policy can run? | `mxc_engine` resolves the backend and checks authorization; host capabilities and backend validation determine what can actually be enforced. | -Typed authoring and raw JSON converge before backend execution: +Typed SDK inputs and "MXC request JSON" converge before backend execution: ```mermaid flowchart LR typed["Typed SDK policy or lifecycle request"] target["SDK-owned published exact target"] - raw["Raw JSON with caller-declared version"] + raw["'MXC request JSON' with caller-declared version"] contract["Registered exact Rust contract"] adapter["Version-specific adapter"] normalized["Shared normalization
ExecutionRequest and typed lifecycle operation"] @@ -180,7 +183,7 @@ state-aware roots, including recursively closed experimental structures. The registered Rust types remain the authority for declared `1.1.0-alpha` requests; the schema is their derived editor and validation artifact. -Raw JSON is parsed with the exact registered contract named by its `version` +"MXC request JSON" is parsed with the registered contract named by its `version` field. High-level Rust, .NET, and Node v1 builders do not accept a caller-supplied schema version: they construct requests for exact `1.0.0` and reject fields or backends outside that contract. Repository config validation selects the schema diff --git a/docs/development/glossary.md b/docs/development/glossary.md new file mode 100644 index 000000000..8b964cb6e --- /dev/null +++ b/docs/development/glossary.md @@ -0,0 +1,48 @@ +# MXC developer glossary and wording guide + +> **Audience:** MXC developers + +Use the preferred wording in consumer prose; keep code identifiers unchanged. +Shared terms: [consumer glossary](../glossary.md). + +## Implementation terminology + +| Existing term | Preferred wording | Specific meaning or example | +|---|---|---| +| State-aware / stateful API | Container lifecycle operations | Provision, start, exec, stop, deprovision; implemented by `StatefulSandboxBackend`. Use "manual lifecycle operations" when emphasizing caller-managed calls and cleanup. | +| One-shot | Create-and-run execution | Make container, run workload, clean up. | +| Exact contract / exact registered contract | Contract | Versioned request rules in `mxc_contract`; identify the version, operation, and provision backend. A schema describes the contract. | +| Wire format / wire contract | Interface-specific format / contract | Name the format: ["MXC request JSON"](../schema.md#mxc-request-json) or daemon binary frames. | +| Wire type / DTO (data transfer object) | Serialization type | Name the exchanged-data type and namespace, not just its role. | +| Closed type / closed enum / closed union | Fixed set of types or values | Enumerated choices; host availability is checked independently. | +| Closed request root / recursively closed schema | Unknown-field rejection | Applies to the top-level request / nested objects too. Name the version and operation. | +| Request root | Top-level request type | Creation or lifecycle input in JSON schema, not a filesystem root. | +| Discriminator | Variant-selecting field | Lifecycle "MXC request JSON" uses `phase`. | +| Binding / checked binding | Checked backend-config conversion | Preserve absent versus present-empty configuration. | +| Backend-neutral | Shared across backends | `mxc_common` models, not backend execution or enforcement. | +| Operation-neutral | Shared across named operations | Name the operations, type, and shared responsibility. | +| Surface / authoring surface | Named API, type, or field | Example: `Microsoft.Mxc.Sdk.V1.ContainerRequest`. | +| Pinned version / hash | Required version / expected checksum | Dependency selection or verification, not log-output assertions. | +| Pinned logging / pin logger output in corpus | Record and assert expected logs | Name the log fields, test inputs, and comparison test. | +| Hosts-file pin / pinned proxy endpoint | Address mapping / address restriction | Name the hosts-file entry or permitted proxy address. | + +## Avoid ambiguous shorthand + +These are wording examples, not feature names. + +| Wording to avoid | Write instead | +|---|---| +| The request / the config / change policy configuration | Name the actual type, namespace, and field, e.g. `mxc_sdk::v1::ContainerRequest`. | +| Unqualified `ExecutionRequest` | SDK input: `mxc_sdk::v1::ExecutionRequest`. Runtime model: `mxc_common::models::ExecutionRequest`. | +| Closed exclusion | Specify field rejection, a predefined diagnostic reason, or denied access. | +| Closes over policy | Specify callback capture, contained policy fields, or validation. | +| Pin output | Record expected output and assert it in a named test. | +| Operation-neutral configuration | Name the shared type and operations. | +| Raw JSON / "Native request" / "SDK request JSON" | Use ["MXC request JSON"](../schema.md#mxc-request-json); typed SDK callers do not author JSON. | + +## Documentation editing rules + +Keep edits local. Name types and fields; define unfamiliar terms. +Use "contract," not "exact contract"; reserve "schema" for its JSON description. +Quote JSON format names and link their definitions on first use. +Keep API names, signatures, paths, and commands unchanged. diff --git a/docs/glossary.md b/docs/glossary.md new file mode 100644 index 000000000..14be787cc --- /dev/null +++ b/docs/glossary.md @@ -0,0 +1,22 @@ +# MXC consumer glossary + +> **Audience:** MXC consumers + +Terms used in the [SDK reference](api-reference/README.md) and [configuration guide](schema.md). Internal terms: [developer glossary](development/glossary.md). + +| Term | Meaning in MXC | Example | +|---|---|---| +| Container | Isolated environment in which a command or program runs. | Process isolation, Linux container, user session, or VM. | +| Backend | MXC implementation of a container technology. | Bubblewrap on Linux. | +| Workload | Command or program running inside the container. | `node -e "console.log('hello')"` | +| Policy / config | Access rules enforced by the container. | `FilesystemPolicy`: readable, writable, and denied paths. | +| **Create-and-run** \(transient\) | Setup, execute, and cleanup all in "one-shot". | Node V1 `run` or `spawn`. | +| **Lifecycle** \(persistable\) | Provision, start, execute, stop, and deprovision. | [Container Lifecycle operations guide](container-lifecycle.md). | +| Persistent / transient container | Retained between calls / cleaned up after a run. | Neither promises survival across host reboots. | +| Provision / deprovision | Allocate / release container resources. | `provisionContainer` / `deprovisionContainer`. | +| Container identity / opaque ID | Provisioned container's `ContainerId`; pass unchanged, never parse or construct it. | Provision returns it; later operations take it. | +| **MXC request JSON** | Versioned JSON request format for containment rules and operations. | [Stable `1.0.0` schema](../schemas/stable/mxc-config.schema.1.0.0.json). | +| **\[I\/O model\]** Captured | Full output emitted after program execution. | Node V1 `run`. | +| **\[I\/O model\]** Streaming output | Output collected after completion / readable during execution. | Node V1 `spawn`. | +| **\[I\/O model\]** PTY (pseudoterminal) | Interactive terminal with input, combined output, and resizing. | Node V1 `spawnWithPty`. | +| **\[Networking\]** Host loopback | Container-to-host loopback communication, not access from other machines. | `network.ingress.hostLoopback`. | diff --git a/docs/logging-access-denied.md b/docs/logging-access-denied.md index 3173c4958..309c656c8 100644 --- a/docs/logging-access-denied.md +++ b/docs/logging-access-denied.md @@ -269,8 +269,8 @@ policy denial occurrences plus diagnostic outcomes omitted from the policy file: ``` Signatures are keyed by symbolic provider category, provider GUID, -provider-scoped event ID, schema name, closed outcome reason, PID, and sorted -sanitized properties. SIDs, capability names, GUIDs, PIDs/process identifiers, and +provider-scoped event ID, schema name, outcome reason from a fixed list, PID, and +sorted sanitized properties. SIDs, capability names, GUIDs, PIDs/process identifiers, and non-file resource values are retained. Complete file paths are replaced with ``; standalone user/account names remain replaced with ``. @@ -284,8 +284,8 @@ under the same signature and increment its count. `accessType` and `resourceType` are included when denial extraction determined them; diagnostic outcomes without those classifications omit the fields. -Candidates excluded from the actionable output retain a closed diagnostic -reason and their sanitized event properties: +Candidates excluded from the actionable output retain a diagnostic +reason from a fixed list and their sanitized event properties: - `notActionable` includes registry writes, registry checks whose access mask cannot be classified as a read, and recognized Section, SymbolicLink, and @@ -319,7 +319,7 @@ the event has no actionable extractor. NetworkDecision records are kept only by unscoped analysis, with PID 0 and that reason; the local file keeps their sanitized properties, including remote endpoints, while telemetry drops them. -Per-event TDH failures use closed diagnostic reasons: +Per-event TDH failures use these predefined diagnostic reasons: `eventPayloadMalformed` means the payload is malformed or conflicts with its declared schema, `decoderLimitReached` means a nesting/element/work safety bound stopped decoding, and `unsupportedPropertyEncoding` means the decoder cannot consume @@ -350,7 +350,7 @@ When stable telemetry is enabled and authorized, MXC may validate, compact, and send this redacted verbose document through `Microsoft.MXC/MXC.VerboseDenials`. Each event contains a valid JSON array of complete signatures and document reconstruction metadata. Before emission, MXC derives provider GUIDs from the -closed provider enum and drops every verbose property name and value. MXC does +predefined provider enum and drops every verbose property name and value. MXC does not send the actionable denials file, workload-derived properties, or raw ETL through telemetry. See [MXC telemetry](development/architecture/telemetry.md). diff --git a/docs/schema.md b/docs/schema.md index 0526af50c..60342e922 100644 --- a/docs/schema.md +++ b/docs/schema.md @@ -5,10 +5,14 @@ MXC uses a JSON configuration file. The current stable schema is at [`schemas/stable/mxc-config.schema.1.0.0.json`](../schemas/stable/mxc-config.schema.1.0.0.json). -For development, the exact schema at +For development, the schema for version `1.1.0-alpha` at [`schemas/dev/mxc-config.schema.1.1.0-alpha.json`](../schemas/dev/mxc-config.schema.1.1.0-alpha.json) includes experimental features and may change without notice. +See the [consumer glossary](glossary.md) for terminology. This guide describes +["MXC request JSON"](#mxc-request-json); typed SDK callers use the types in the +[SDK reference](api-reference/README.md) instead. + Editors that support JSON Schema will provide autocomplete and validation when you add a `"$schema"` reference to your config file. Use the stable schema for production configs and the dev schema when working on experimental features: @@ -21,6 +25,53 @@ production configs and the dev schema when working on experimental features: "$schema": "./schemas/dev/mxc-config.schema.1.1.0-alpha.json" ``` +### "MXC request JSON" + +"MXC request JSON" is a versioned request object defined by the +[published schemas](../schemas/stable/) or +[development schema](../schemas/dev/mxc-config.schema.1.1.0-alpha.json). +Executors and `mxc_ffi`, MXC's C-callable library, use the same registered +contracts. There is no additional free-form FFI format. + +Typed Rust, .NET, and Node SDK callers use `ContainerRequest`, +`ProvisionRequest`, and `ExecutionRequest`, not JSON. Node and .NET produce +"MXC request JSON" internally. + +Creation requests omit `phase` and `sandboxId`. Lifecycle requests include +`phase`, and operations after provision include `sandboxId`. For executor +lifecycle calls, supply those values through `--operation` and `--container-id`, +not in the JSON file. The executor inserts them before using the same contract +parser. + +For example, starting a provisioned WSLc container uses this "MXC request JSON" +with `mxc_run_state_aware_json`: + +```json +{ + "version": "1.0.0", + "phase": "start", + "sandboxId": "wslc:0123456789abcdef0123456789abcdef" +} +``` + +To supply the same request through the executor, save the non-routing fields +as `start.json`: + +```json +{ + "version": "1.0.0" +} +``` + +```text +wxc-exec.exe start.json --operation start --container-id wslc:0123456789abcdef0123456789abcdef +``` + +Replace the example ID with the one returned by provision. The C-callable +functions take NUL-terminated UTF-8 JSON strings. The schemas specify fields +and values; backend validation checks whether the requested policy can run. +For full lifecycle examples, see [container lifecycle JSON requests](#container-lifecycle-json-requests). + ### Directional networking (supported contracts) Supported contracts from `0.9.0-alpha` use explicit egress and ingress policy @@ -86,13 +137,13 @@ to a named proxy peer. It does not enforce a proxy-only host-loopback exception. The `defaultPolicy`, `enforcementMode`, `allowLocalNetwork`, `allowedHosts`, `blockedHosts`, and `network.proxy` fields belonged to retired contracts. -No supported exact contract accepts them. Migrate existing policies to +No supported contract accepts them. Migrate existing policies to directional fields and `runtimeConfig.networkProxy` rather than changing the version string alone. ### IsolationSession unrestricted networking (0.9) -IsolationSession cannot restrict networking. Exact v0.9 requests must describe +IsolationSession cannot restrict networking. JSON requests for `0.9.0-alpha` must describe that actual posture through the standard directional network fields: ```json @@ -244,13 +295,13 @@ that can be executed independently. } ``` -> **State-aware fields.** The `phase` top-level field is the **state-aware -> discriminator**: a request that includes it is parsed as a state-aware -> lifecycle request (see below), *not* the one-shot config above. The `sandboxId` -> top-level field is state-aware-only — a one-shot request carrying `sandboxId` +> **Lifecycle operation fields.** The `phase` top-level field selects a +> container lifecycle operation: JSON that includes it is parsed as input for +> that operation (see below), *not* the create-and-run configuration above. The `sandboxId` +> top-level field is only for lifecycle operations; a create-and-run JSON request carrying `sandboxId` > is rejected with a parse error. Callers cannot supply `correlationVector`; > it is rejected as an unknown field because lifecycle correlation is internal -> to MXC and is not part of the request or response contract. See +> to MXC and is not a declared JSON request or response field. See > [Container lifecycle architecture](development/architecture/container-lifecycle.md) > and [telemetry architecture](development/architecture/telemetry.md). @@ -276,10 +327,10 @@ use: Policy entries that are blank, name a file, or do not exist yet are skipped: a process cannot be launched in any of them. -WSL Container one-shot runs accept an explicit `process.cwd` only as a local +WSL Container create-and-run execution accepts an explicit `process.cwd` only as a local Windows drive path, which is mapped under `/mnt/` (for example `C:\work` becomes `/mnt/c/work`); any other value is rejected before the -container is created. WSL Container state-aware `exec` takes an absolute +container is created. WSL Container execution in an existing container (`exec`) takes an absolute in-container path instead. See [`docs/backends/wslc/wsl-container-getting-started.md`](backends/wslc/wsl-container-getting-started.md). @@ -383,9 +434,9 @@ refuse any supplied `ui` at every phase on both surfaces**, and each accepts an omitted one without applying any UI restriction — so the section's default-deny reading does not hold on either. The reasons differ: no `ui` posture is truthful for a session-isolated sandbox (see -[IsolationSession state-aware Rust architecture](development/architecture/backends/isolation-session/state-aware-rust.md)), +[IsolationSession Rust lifecycle architecture](development/architecture/backends/isolation-session/state-aware-rust.md)), while WSLc has no mechanism to enforce UI restrictions on a container (see -[`backends/wslc/wslc-state-aware.md`](backends/wslc/wslc-state-aware.md)). +[WSLc container lifecycle guide](backends/wslc/wslc-state-aware.md)). The Windows `processContainer.ui` sub-block carries the ProcessContainer-only fields `isolation`, `desktopSystemControl`, `systemSettings`, and `ime`. `processContainer.filesystem` carries `enumeratePaths`. Both sub-blocks are @@ -419,12 +470,12 @@ force a particular backend. | Value | Description | |-------|-------------| | `"processcontainer"` | (Default) Windows process-level isolation. Resolves to AppContainer (legacy) or BaseContainer (newer OS sandbox API) at run time depending on host capabilities and the `--experimental` flag. | -| `"windows_sandbox"` | Windows Sandbox VM isolation. Dual-mode: a transient **one-shot** runner that launches a fresh disposable VM per execution, and a **state-aware** lifecycle backed by a long-lived per-sandbox daemon. | +| `"windows_sandbox"` | Windows Sandbox VM isolation. Supports create-and-run execution with a fresh disposable VM per execution, and container lifecycle operations backed by a long-lived per-container daemon. | | `"wslc"` | Linux containers via the WSL Container SDK | | `"lxc"` | Native LXC container isolation. No abstract intent resolves to LXC; request it explicitly. | | `"microvm"` | MicroVM isolation via Windows HyperV Platform (NanVix microkernel) | | `"hyperlight"` | MicroVM isolation via Hyperlight + Unikraft with an embedded CPython snapshot (experimental) | -| `"isolation_session"` | Windows isolation session — runs the workload as a freshly-provisioned, per-execution isolated user account in its own OS-managed session. Dual-mode: one-shot and state-aware. | +| `"isolation_session"` | Windows isolation session: create-and-run execution uses a freshly provisioned isolated user account in its own OS-managed session. Container lifecycle operations can retain the session between executions. | | `"seatbelt"` | macOS sandbox isolation (Seatbelt). Requires macOS 15 or later — see [`docs/backends/seatbelt/seatbelt-backend.md`](backends/seatbelt/seatbelt-backend.md). | | `"bubblewrap"` | Unprivileged Linux sandboxing via Bubblewrap/user namespaces. The Linux default — see [`docs/backends/bwrap/bubblewrap-backend.md`](backends/bwrap/bubblewrap-backend.md). | @@ -432,15 +483,18 @@ Only the backend section matching the selected `containment` value is accepted; a config that also carries an unrelated backend's section is **rejected** with a "Multiple containment backends configured" error rather than silently ignored. -### State-aware lifecycle envelope +### Container lifecycle JSON requests + +These examples use ["MXC request JSON"](#mxc-request-json). For executor +calls, use the conversion described above. -The exact development schema documents a multi-phase envelope shape for the -state-aware lifecycle (`provision` / `start` / `exec` / `stop` / -`deprovision`). Where the one-shot config above is a self-contained -`ExecutionRequest` to run once, a state-aware envelope identifies which -phase is being driven against an existing provisioned sandbox. +The development schema documents JSON objects for container lifecycle +operations (`provision` / `start` / `exec` / `stop` / `deprovision`). +The create-and-run JSON above describes container setup and a workload together. +Lifecycle JSON instead identifies an operation: `provision` allocates a new +container; later operations use the returned `sandboxId`. -State-aware envelopes use an exact backend-specific contract: +Choose a supported contract version for the backend and operation: - IsolationSession uses published `0.9.0-alpha`. - WSLC uses published `0.9.0-alpha`, or development `1.1.0-alpha` for @@ -448,9 +502,9 @@ State-aware envelopes use an exact backend-specific contract: `1.1.0-alpha`. Contracts before `0.9.0-alpha` are retired. The supported published -`0.9.0-alpha` and `1.0.0` contracts contain one-shot plus IsolationSession -and WSLC state-aware request roots. This Windows Sandbox example therefore uses the -exact development schema: +`0.9.0-alpha` and `1.0.0` contracts describe create-and-run JSON requests plus +IsolationSession and WSLC lifecycle JSON requests. This Windows Sandbox example therefore uses the +development schema for `1.1.0-alpha`: ```json { @@ -464,7 +518,7 @@ exact development schema: // (the backend is inferred from sandboxId). "process": { "commandLine": "echo hi" } // Cross-cutting fields (process / filesystem / network / ui) sit at the TOP - // level, exactly as in a one-shot request -- there is no wrapping `config` + // level, exactly as in a create-and-run request -- there is no wrapping `config` // object. Backend- and phase-specific config, when a phase has any, nests // under its permanent backend section, e.g.: // "isolationSession": { "provision": { "appId": "PFN:Contoso.App_8wekyb3d8bbwe" } } @@ -481,12 +535,12 @@ Phase / sandboxId / containment validation: | `stop` | **Required** | Ignored if present | | `deprovision` | **Required** | Ignored if present | -State-aware-capable backends today are `isolation_session`, `windows_sandbox`, +Backends supporting container lifecycle operations today are `isolation_session`, `windows_sandbox`, and `wslc` (all Windows-only). IsolationSession does not require runtime experimental authorization; Windows Sandbox does. WSLC `provision` accepts `wslc.provision.portMappings`, the same -`windowsPort` / `containerPort` / `protocol` entries as the one-shot +`windowsPort` / `containerPort` / `protocol` entries as the create-and-run `wslc.portMappings` list, applied to the sandbox's own container: ```json @@ -515,14 +569,14 @@ posture is rejected at provision. Two entries claiming the same `windowsPort` are rejected on both surfaces. The session sizing knobs (`cpuCount` / `memoryMb` / `gpu` / `storagePath`) stay -one-shot-only, because the state-aware daemon shares one WSL session across +available only for create-and-run execution, because the lifecycle daemon shares one WSL session across every sandbox and cannot size them individually. Full lifecycle API: [container lifecycle](container-lifecycle.md). ### Schema Versioning -MXC execution config files require a `version` field naming an exact registered +MXC execution config files require a `version` field naming a supported contract. Version spelling, including patch and prerelease, is significant; there is no range, latest-version, or missing-version fallback. @@ -531,13 +585,13 @@ yet stable — breaking changes may occur before publication. Version `1.0.0` is the first stable contract. After `1.0.0`, breaking changes require a major version bump per semver. -Registered contracts: +Supported contracts: | Config `version` | Status | |---|---| | `"0.9.0-alpha"` | Published; minimum supported | | `"1.0.0"` | Published; current stable | -| `"1.1.0-alpha"` | Mutable development contract | +| `"1.1.0-alpha"` | Development contract; may change | An absent version, a retired version, or any unregistered spelling such as `0.6.1-alpha`, `0.10.0-alpha`, or `1.0.1` is rejected. diff --git a/samples/README.md b/samples/README.md index d726b2879..b963190ff 100644 --- a/samples/README.md +++ b/samples/README.md @@ -22,7 +22,7 @@ compare while each implementation remains idiomatic for its language. ## Conventions - Each sample demonstrates one primary concept through the public versioned V1 API. -- Requests use typed SDK models rather than JSON or generated wire types. +- Requests use typed SDK models rather than ["MXC request JSON"](../docs/schema.md#mxc-request-json) or generated JSON serialization types. - Commands are harmless, bounded, and selected for the current operating system. - Workload failures, timeouts, SDK errors, warnings, and cleanup failures remain visible. - Projects reference the SDK in this repository so the samples track the current source. diff --git a/samples/run-with-containment-of-network/README.md b/samples/run-with-containment-of-network/README.md index f6f65248b..98857ccec 100644 --- a/samples/run-with-containment-of-network/README.md +++ b/samples/run-with-containment-of-network/README.md @@ -7,7 +7,7 @@ transient container with outbound, inbound, and host-loopback networking explicitly denied. The contained `curl` request must fail; a successful request causes the sample to fail. -The host's native process-isolation backend must support this deny-all posture. +The host's native process-isolation backend must support denying all network access. Unsupported policy fails before creating the container. ## Run diff --git a/sdk/dotnet/README.md b/sdk/dotnet/README.md index b78535c95..ab5549bcd 100644 --- a/sdk/dotnet/README.md +++ b/sdk/dotnet/README.md @@ -5,8 +5,8 @@ `Microsoft.Mxc.Sdk` provides .NET APIs for authoring and executing MXC container requests through the in-process native `mxc_ffi` library. The versioned public API is in `Microsoft.Mxc.Sdk.V1`. All request and policy types -in this API use the owned V1 contract rather than exposing a wire-version -selector. +in this API use the SDK-selected contract version; callers do not select +that version. See the [consumer glossary](https://github.com/microsoft/mxc/blob/main/docs/glossary.md) for terminology. ## Run to completion @@ -49,13 +49,13 @@ Console.WriteLine($"exit={result.ExitCode}"); `Spawn` / `SpawnAsync` return a live `MxcProcess` with separate stdin, stdout, and stderr streams, plus wait, termination, and disposal operations. Shared filesystem, network, and UI restrictions are authored directly on `ContainerRequest`, -alongside the selected backend configuration. The SDK owns the exact wire -contract; requests do not accept a caller-selected schema version. +alongside the selected backend configuration. The SDK selects the contract +version; `ContainerRequest` does not accept a caller-selected schema version. `UiPolicy.Disable` defaults to `true`; clipboard and input-injection permissions are authored separately. -`ContainerRequest.Containment` is a closed `Containment` choice. Select an +`ContainerRequest.Containment` accepts the SDK-defined `Containment` choices. Select an SDK-owned nested choice such as `Containment.Process` (the default), `Containment.ProcessContainer`, or `Containment.Wslc`. Backend semantics are validated by the native engine. @@ -113,7 +113,7 @@ Unsupported combinations are rejected before sandbox creation. `ProvisionResult.Metadata` is an optional `ProvisionMetadata` value. For IsolationSession, pattern-match `IsolationSessionProvisionMetadata` to read the agent account and workspace details. WSLC returns no provision metadata. Native -metadata is mapped to this closed typed surface; callers do not parse raw JSON. +metadata is converted to these SDK-defined types; callers do not parse native result JSON. `MxcLifecycle` provides typed provision, start, exec, stop, and deprovision operations. The provision result contains an opaque `ContainerId`; pass it to diff --git a/sdk/node/README.md b/sdk/node/README.md index b757b6591..6d49b8491 100644 --- a/sdk/node/README.md +++ b/sdk/node/README.md @@ -5,8 +5,9 @@ Node.js / TypeScript SDK for MXC (Microsoft eXecution Containers). The versioned public request, execution, and lifecycle APIs are exported from `@microsoft/mxc-sdk/v1`, including platform discovery, errors, and telemetry consent. -The package root exports no public APIs. These APIs use the SDK-owned V1 contract; callers -do not select the wire version. +The package root exports no public APIs. These APIs use the SDK-selected contract +version; callers do not select it. See the [consumer glossary](https://github.com/microsoft/mxc/blob/main/docs/glossary.md) +for terminology. ```bash npm install @microsoft/mxc-sdk @@ -68,8 +69,8 @@ Each operation accepts its own optional options type: `RunOptions`, Execution options do not support `dryRun`. `ContainerRequest` holds the command, cross-backend filesystem, network, and UI -settings, and the selected backend's typed configuration. The SDK selects its -exact V1 contract; callers do not provide a schema version or raw executor +settings, and the selected backend's typed configuration. The SDK selects the +contract version for V1; callers do not provide a schema version or executor JSON configuration. When UI settings are supplied, `ui.disable` explicitly controls whether UI is @@ -156,7 +157,7 @@ IsolationSession exec with a caller-driven terminal and returns a `Promise`. Set `options.size` for initial dimensions; it defaults to 24 rows by 80 columns. IsolationSession provision requires an explicit unrestricted directional -network posture; WSLC network posture is fixed at provision. See the +network permissions; WSLC network permissions are fixed at provision. See the [IsolationSession](https://github.com/microsoft/mxc/blob/main/docs/development/architecture/backends/isolation-session/state-aware-typescript.md) and [WSLC](https://github.com/microsoft/mxc/blob/main/docs/backends/wslc/wslc-state-aware.md) guides for backend and phase requirements. @@ -183,12 +184,12 @@ The runtime values are typed as `NetworkRuntimeConfig`. The creation containment types are compile-time-only choices under the `Containment` namespace, such as `Containment.Process` and -`Containment.ProcessContainer`. `Containment` is also their closed union; +`Containment.ProcessContainer`. `Containment` is the union of these SDK-defined types; the SDK does not create runtime containment objects or factories. `ContainerRequest` uses the named `FilesystemPolicy`, `NetworkPolicy`, and `UiPolicy` types for cross-backend restrictions. Backend-specific settings -remain on the selected containment configuration. +remain on the selected `Containment` member, such as `Containment.ProcessContainer`. ## Public V1 types diff --git a/src/mxc-sdk/README.md b/src/mxc-sdk/README.md index a8caa50a4..b5b68029e 100644 --- a/src/mxc-sdk/README.md +++ b/src/mxc-sdk/README.md @@ -8,6 +8,8 @@ them in-process through the native engine. The versioned public API is under network, and UI restrictions; its typed containment value selects and configures the backend. +See the [consumer glossary](https://github.com/microsoft/mxc/blob/main/docs/glossary.md) for terminology. + ## Run to completion and spawn Build a `ContainerRequest` directly, then choose captured output with `run` or @@ -133,7 +135,7 @@ operation options and return no execution output. | Terminal process outcome | `v1::WaitResult` | `v1::spawn_with_pty` creates a container with a caller-controlled terminal. -One-shot PTY support is available for IsolationSession on Windows, Bubblewrap +Create-and-run PTY support is available for IsolationSession on Windows, Bubblewrap and LXC on Linux, and Seatbelt direct execution on macOS. LXC requires root. Seatbelt rejects PTY mode with `guiAccess` or legacy `launchMethod: "open"`. `wait()` requests canonical-mode terminal EOF for untaken input; raw-mode @@ -144,7 +146,7 @@ For host discovery, use `mxc_sdk::v1::platform_support` and `mxc_sdk::v1::available_backends`. Errors are returned as `mxc_sdk::v1::Error` with an `ErrorCode`. Telemetry and policy helpers are also under `v1`. The -[launch-choice table](../../docs/api-reference/rust/v1/api.md#choosing-a-launch-operation) +[launch-choice table](https://github.com/microsoft/mxc/blob/main/docs/api-reference/rust/v1/api.md#choosing-a-launch-operation) compares captured, piped, and terminal execution. ## Build features and backend support diff --git a/src/mxc-sdk/src/core/mxc_engine/error.rs b/src/mxc-sdk/src/core/mxc_engine/error.rs index 66ad32c6b..9135ecb22 100644 --- a/src/mxc-sdk/src/core/mxc_engine/error.rs +++ b/src/mxc-sdk/src/core/mxc_engine/error.rs @@ -6,8 +6,8 @@ use crate::mxc_common::mxc_error::{MxcError, MxcErrorCode}; -/// Closed set of error codes the SDK can return. Mirrors the wire-format codes -/// (serialised as snake_case strings) one-for-one. +/// Error codes returned by the SDK, corresponding one-for-one to the +/// snake_case codes reported by MXC. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ErrorCode { MalformedRequest, @@ -25,7 +25,7 @@ pub enum ErrorCode { } impl ErrorCode { - /// The wire-format (snake_case) string for this code. + /// The snake_case value used in JSON errors, such as `"backend_error"`. pub fn as_str(self) -> &'static str { match self { Self::MalformedRequest => "malformed_request", @@ -69,21 +69,18 @@ impl From for ErrorCode { } } -/// An error returned by the SDK's fallible operations -/// ([`spawn_execution_request`](crate::spawn_execution_request)). +/// An error returned by SDK operations such as [`spawn`](crate::v1::spawn). /// -/// The detail fields sit flat on the error, the same way the wire format, the -/// C ABI and the C# SDK carry them — one failure reads the same whichever of -/// the four surfaces a caller is holding. +/// Details such as `operation` and `native_code` are top-level fields, not +/// nested in another object. The Node and .NET SDKs and errors returned through +/// `mxc_ffi` expose the same fields. /// -/// Marked `#[non_exhaustive]`, as both the wire envelope and the internal error -/// this facades already are: read the fields, and build one with -/// [`Error::new`] rather than by literal, so a later field costs a downstream -/// crate nothing. +/// This type is `#[non_exhaustive]`. Use [`Error::new`] rather than a struct +/// literal so future versions can add fields without breaking construction. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub struct Error { - /// The closed error code. + /// The SDK error code. pub code: ErrorCode, /// A human-readable message. pub message: String, diff --git a/src/mxc-sdk/src/core/mxc_engine/platform.rs b/src/mxc-sdk/src/core/mxc_engine/platform.rs index 74fd40341..393079e38 100644 --- a/src/mxc-sdk/src/core/mxc_engine/platform.rs +++ b/src/mxc-sdk/src/core/mxc_engine/platform.rs @@ -39,7 +39,7 @@ pub struct PlatformSupport { pub is_supported: bool, /// Why the platform is unsupported, when `is_supported` is false. pub reason: Option, - /// Containment backends available on this host, by wire name + /// Containment backends available on this host, using their JSON `containment` values /// (e.g. `"seatbelt"`, `"bubblewrap"`, `"processcontainer"`). /// /// Reports that the backend's tooling is present and usable, not that the @@ -131,7 +131,7 @@ where /// reported when the host has the WSL Container runtime (requires the `wslc` /// feature). The broader host-capability set (backends the host can run but the /// SDK cannot launch) is reported separately by -/// [`available_backends`](crate::available_backends). +/// [`available_backends`](crate::v1::available_backends). /// /// Every probe here answers "is the tooling usable", not "may this process use /// it" — see [`PlatformSupport::available_methods`]. diff --git a/src/mxc-sdk/src/core/mxc_engine/probe.rs b/src/mxc-sdk/src/core/mxc_engine/probe.rs index 86ee60b36..428aa6207 100644 --- a/src/mxc-sdk/src/core/mxc_engine/probe.rs +++ b/src/mxc-sdk/src/core/mxc_engine/probe.rs @@ -43,7 +43,7 @@ pub enum BackendCapability { #[derive(Debug, Clone, PartialEq, Eq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AvailableBackend { - /// Canonical [`ContainmentBackend::wire_name`] value. + /// Backend identifier, such as `"seatbelt"` or `"processcontainer"`. pub backend: String, /// Highest-isolation tier the host supports for this backend (a canonical /// `IsolationTier::as_str()` name); `None`, and omitted from JSON, for diff --git a/src/mxc-sdk/src/lib.rs b/src/mxc-sdk/src/lib.rs index 99e3d1c2c..cef215ddf 100644 --- a/src/mxc-sdk/src/lib.rs +++ b/src/mxc-sdk/src/lib.rs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! `mxc-sdk` — an importable library for running MXC containers in-process. +//! `mxc-sdk`: an importable library for running MXC containers in-process. +//! +//! See the [consumer glossary](https://github.com/microsoft/mxc/blob/main/docs/glossary.md) +//! for terminology. //! //! Create a [`v1::ContainerRequest`] with its command and restrictions, then either: //! @@ -64,9 +67,8 @@ //! //! # Diagnosing a failure //! -//! [`Error`] carries a closed [`ErrorCode`] and a message, and — when the -//! failure came from an underlying platform API — the call that failed and its -//! status: +//! [`Error`] carries an [`ErrorCode`] and a message. When an underlying platform +//! API fails, it also identifies the call and its status: //! //! ```no_run //! # fn demo(error: mxc_sdk::v1::Error) { @@ -80,7 +82,7 @@ //! ``` //! //! [`Error::operation`] and [`Error::native_code`] are absent for a failure -//! raised before any API call was reached — a malformed policy, say — and a +//! raised before any API call was reached (a malformed policy, say) and a //! native code only ever appears alongside the operation it belongs to. //! `Display` renders both, so logging the error alone does not lose them. //! @@ -119,9 +121,9 @@ //! can take the streams from [`MxcProcess`] or let `wait` drain and discard any //! stream they did not take. WSLC does not expose stdin. //! -//! [`v1::spawn_with_pty`] allocates a caller-controlled PTY for supported one-shot -//! backends, including IsolationSession, Bubblewrap, LXC, and Seatbelt direct -//! execution. +//! [`v1::spawn_with_pty`] allocates a caller-controlled PTY for create-and-run +//! execution on supported backends, including IsolationSession, Bubblewrap, LXC, +//! and Seatbelt direct execution. //! [`v1::container::spawn_in_container_with_pty`] does the same for a process in //! an existing IsolationSession container. Unsupported backends reject the //! request before creating a sandbox. @@ -416,7 +418,7 @@ pub mod __ffi { /// Run a lifecycle request (as a JSON string) and return the /// response-envelope JSON string. /// - /// Handles the envelope phases — `provision`, `start`, `stop`, `deprovision` — + /// Handles the envelope phases (`provision`, `start`, `stop`, `deprovision`) /// and a dry run of any phase. A non-dry-run execution produces no envelope, so /// it is rejected here; use an execution entry point instead: /// [`execute_lifecycle_json`] to drive the pipes yourself. @@ -441,7 +443,7 @@ pub mod __ffi { /// Execute a lifecycle request (as a JSON string) as a **live streaming** /// process, returning a [`MxcProcess`] handle for output streaming, waiting, - /// and termination — exactly like [`v1::spawn`]. Backends that expose process + /// and termination, exactly like [`v1::spawn`]. Backends that expose process /// input also make [`MxcProcess::take_stdin`] available. /// /// The request JSON must specify the `exec` lifecycle phase (with a diff --git a/src/mxc-sdk/src/policy.rs b/src/mxc-sdk/src/policy.rs index b0c9ca397..86b23be93 100644 --- a/src/mxc-sdk/src/policy.rs +++ b/src/mxc-sdk/src/policy.rs @@ -598,8 +598,8 @@ pub enum Containment { /// IsolationSession backend: a Windows isolated user session. /// /// Requires the `isolation_session` build feature but no runtime - /// experimental opt-in. Served with piped stdio; the multi-call lifecycle - /// is reachable through the state-aware entry points. + /// experimental opt-in. Uses piped stdio. Manual lifecycle operations are + /// available through [`crate::v1::container`]. IsolationSession, } @@ -645,7 +645,7 @@ pub(crate) struct ContainerPolicy { pub timeout_ms: Option, } -/// A complete one-shot request with shared restrictions and backend settings. +/// Request for creating a container and running a workload. #[derive(Debug, Clone)] pub struct ContainerRequest { /// Command line to execute. @@ -672,7 +672,7 @@ pub struct ContainerRequest { } impl ContainerRequest { - /// Create a one-shot request for `command`. + /// Create a [`ContainerRequest`] for `command`. pub fn new(command: impl Into) -> Self { Self { command: command.into(), diff --git a/src/mxc-sdk/src/sandbox.rs b/src/mxc-sdk/src/sandbox.rs index a383fcdbc..78c2a9353 100644 --- a/src/mxc-sdk/src/sandbox.rs +++ b/src/mxc-sdk/src/sandbox.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! The SDK's sandbox handle — a crate-owned facade over the internal +//! The SDK's sandbox handle: a crate-owned facade over the internal //! `mxc_common` streaming handle, so the public API never exposes the //! foundation crate's traits. @@ -221,8 +221,8 @@ pub enum WaitResult { /// /// How far "gone" reaches depends on the backend. A process-spawning /// backend kills the whole tree. A backend whose only primitive is the - /// foreground process — the state-aware `exec` path over IsolationSession — - /// confirms that process, and a descendant the workload backgrounded is + /// foreground process, such as IsolationSession execution in an existing + /// container, confirms that process, and a descendant the workload backgrounded is /// reclaimed when the container is stopped and deprovisioned rather than here. /// /// That lifecycle route is reachable through @@ -254,7 +254,7 @@ pub struct ExecutionResult { /// and [`spawn_in_container`](crate::v1::container::spawn_in_container). /// /// Stream the child's stdio with the `take_*` accessors, wait for it, or kill -/// it. No pty is allocated — the streams are ordinary pipes. Any stdout/stderr +/// it. No pty is allocated; the streams are ordinary pipes. Any stdout/stderr /// the caller does not `take_*` is drained and discarded by [`wait`](Self::wait). pub struct MxcProcess { inner: Box, diff --git a/src/mxc-sdk/src/telemetry.rs b/src/mxc-sdk/src/telemetry.rs index df900d7dc..701c96121 100644 --- a/src/mxc-sdk/src/telemetry.rs +++ b/src/mxc-sdk/src/telemetry.rs @@ -49,7 +49,7 @@ pub enum ConsentState { } impl ConsentState { - /// The stable wire string for this state, identical across every SDK. + /// The stable serialized value for this state, identical across SDKs. pub fn as_str(&self) -> &'static str { Self::to_inner(*self).as_str() } @@ -160,7 +160,7 @@ pub enum ConsentStatusReason { } impl ConsentStatusReason { - /// Stable wire string used by the maintenance and binding contracts. + /// Stable string used in telemetry consent status responses. pub fn as_str(&self) -> &'static str { match self { Self::NoRecord => "no-record", @@ -223,10 +223,10 @@ pub enum ConsentActionResult { } impl ConsentActionResult { - /// Stable wire string used by the maintenance and binding contracts. + /// Stable string used in telemetry consent results. /// - /// The FFI JSON `result` field and language bindings use these exact - /// camelCase spellings. + /// Telemetry-consent FFI operations and the other SDKs use these camelCase + /// spellings in the `result` field. pub fn as_str(&self) -> &'static str { match self { Self::Granted => "granted", @@ -313,7 +313,7 @@ pub enum PolicyState { } impl PolicyState { - /// The stable wire string for this state, identical across every SDK. + /// The stable serialized value for this state, identical across SDKs. pub fn as_str(&self) -> &'static str { Self::to_inner(*self).as_str() }