Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion docs/api-reference/dotnet/v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ Filesystem discovery helpers use `Microsoft.Mxc.Sdk.V1.Policy.Filesystem`.
Discovery, probes, telemetry, and policy helpers use the same versioned boundary.

- [Operation signatures](api.md)
- [V1.Dev exact-JSON API](https://github.com/microsoft/mxc/blob/main/docs/api-reference/dotnet/v1/dev.md)
- [V1.Dev MXC request JSON API and policy preparation](https://github.com/microsoft/mxc/blob/main/docs/api-reference/dotnet/v1/dev.md)
- [Types and fields](types.md)
- [Cross-SDK requirements](../../README.md)

Request data and operation controls are separate. Initial PTY size belongs to the corresponding PTY options; runtime resize remains a process-handle operation. Backend policy enforcement and support checks remain native.

V1.Dev adds separate Windows ProcessContainer policy preparation.
[Preparation types and public JSON helpers](dev.md#policy-preparation-and-caller-owned-observations)
support typed extraction from `MxcException.Details` and lossless
application-owned storage, including Native AOT.
2 changes: 1 addition & 1 deletion docs/api-reference/dotnet/v1/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ IsolationSession-only. Seatbelt PTY rejects `guiAccess`. Terminal handles
give the caller explicit input, output, resize, and process ownership; there
is no separate public attached-console launch API. Typed V1 does not accept
["MXC request JSON"](https://github.com/microsoft/mxc/blob/main/docs/schema.md#mxc-request-json); the
[V1.Dev exact-JSON API](https://github.com/microsoft/mxc/blob/main/docs/api-reference/dotnet/v1/dev.md)
[V1.Dev MXC request JSON API and policy preparation](https://github.com/microsoft/mxc/blob/main/docs/api-reference/dotnet/v1/dev.md)
accepts caller-authored requests separately.

Captured existing-container execution closes its output readers on timeout
Expand Down
266 changes: 262 additions & 4 deletions docs/api-reference/dotnet/v1/dev.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# .NET V1.Dev exact-JSON API
# .NET V1.Dev MXC request JSON API and policy preparation

> **Audience:** MXC consumers

Expand All @@ -11,13 +11,22 @@ Phase-specific methods require the matching JSON `phase`.
Use `JsonOptions` for non-PTY calls and `PtyJsonOptions` for PTY calls.
Both define `Experimental`, independently of the JSON version; PTY options
also define `MxcPtySize? Size` (24 rows by 80 columns by default).
Creation methods execute the supplied request strictly. `PreparePolicyJson`
and its async form take observations separately and use
`PolicyPreparationOptions`, which defines only `Experimental`. Preparation
does not create a container; callers decide whether to accept its proposed
changes.
Execution returns V1 result and process types. Lifecycle and validation
return complete response JSON strings, including backend metadata. When
importing both namespaces, qualify the Dev facade, for example with
`using Dev = Microsoft.Mxc.Sdk.V1.Dev;`.

```csharp
// Microsoft.Mxc.Sdk.V1.Dev.MxcContainer
PreparedPolicyRequest PreparePolicyJson(string json, PolicyFailures failures,
PolicyPreparationOptions? options = null);
Task<PreparedPolicyRequest> PreparePolicyJsonAsync(string json, PolicyFailures failures,
PolicyPreparationOptions? options = null, CancellationToken cancellationToken = default);
ExecutionResult RunJson(string json, JsonOptions? options = null);
Task<ExecutionResult> RunJsonAsync(string json, JsonOptions? options = null,
CancellationToken cancellationToken = default);
Expand Down Expand Up @@ -56,13 +65,262 @@ string ValidateProcessJson(string json, JsonOptions? options = null);
Task<string> ValidateProcessJsonAsync(string json, JsonOptions? options = null);
```

PTY methods are synchronous. Async run/spawn methods accept cancellation
tokens. Canceling `RunJsonAsync` stops awaiting, not native execution;
canceling `RunInContainerJsonAsync` disposes its owned process. Async
PTY methods are synchronous. Async preparation, run, and spawn methods accept
cancellation tokens. Canceling `RunJsonAsync` stops awaiting, not native
execution; canceling `PreparePolicyJsonAsync` similarly stops awaiting, not
preparation.
Canceling `RunInContainerJsonAsync` disposes its owned process. Async
lifecycle management and validation calls run off the caller's thread
without cancellation tokens.

Captured output is lossy UTF-8 text. Callers own and dispose live handles;
existing-container execution leaves the container caller-owned.
A descendant holding an output pipe open can delay captured execution
completion after a workload timeout.

## Policy preparation and caller-owned observations

`PreparePolicyJson(json, failures, options)` validates untrusted creation-policy
observations against the current MXC creation request and proposes only
supported restrictive changes for Windows ProcessContainer. It creates no
container and returns complete prepared request JSON plus a change report.
Unsupported backends, phases, actions, and unsafe changes fail explicitly.

Applications own storage, acceptance of the proposed changes, and the decision
to make another strict creation call. Observations are not policy approval and
do not establish that a resource is complete or that a change is safe. The
creation backend reevaluates the current creator's policy on every attempt.
Each creation call makes one native creation attempt and executes only the
supplied request. MXC keeps no observations between calls.

All types below belong to `Microsoft.Mxc.Sdk.V1.Dev`:

```csharp
public sealed class JsonOptions
{
public bool Experimental { get; set; }
}

public sealed class PolicyPreparationOptions
{
public bool Experimental { get; set; }
}

public enum PolicyChangeOperation
{
Add,
Remove,
Replace,
}

public sealed class PolicyChange
{
public PolicyChangeOperation Operation { get; init; }
public string Path { get; init; }
public JsonElement Before { get; init; }
public JsonElement After { get; init; }
}

public sealed class PreparedPolicyRequest
{
public string RequestJson { get; init; }
public PolicyChange[] Changes { get; init; }

public string ToJson();
public static PreparedPolicyRequest FromJson(string json);
}

public sealed class PolicyFailures
{
public uint FormatVersion { get; init; } = 1;
public PolicyFailureResult[] Results { get; init; } = [];

public string ToJson();
public static PolicyFailures FromJson(string json);
public static PolicyFailures? FromException(MxcException exception);
public PolicyFailures MergedWith(PolicyFailures other);
}

public sealed class PolicyFailureResult
{
public uint Version { get; init; } = 1;
public uint MinimumReaderVersion { get; init; } = 1;
public uint Outcome { get; init; }
public PolicyFailureDetail[] Details { get; init; } = [];
}

public sealed class PolicyFailureDetail
{
public uint FailureClass { get; init; }
public uint FailureReason { get; init; }
public uint RequiredAction { get; init; }
public uint ResourceKind { get; init; }
public uint ValueKind { get; init; }
public uint Flags { get; init; }
public ulong RequestedValue { get; init; }
public ulong RequiredValue { get; init; }
public ushort[]? Resource { get; init; }

public string? GetResourceText();
}
```

### Prepared request and change report

The result JSON is
`{ "requestJson": "...", "changes": [{ "operation": "replace", "path": "/example", "before": null, "after": true }] }`.
`requestJson` contains complete MXC request JSON. `path` is an RFC 6901 JSON
Pointer; `~0` escapes `~` and `~1` escapes `/`. Changes retain their order.
The enum uses the named lowercase JSON strings `add`, `remove`, and `replace`,
not numeric enum values.

Both `before` and `after` are required JSON values, represented by non-nullable
`JsonElement` properties. Add uses JSON null for the absent original value;
Remove uses JSON null for the absent prepared value. Replace can have an actual
JSON null on either side. The operation distinguishes absence from replacing
an actual JSON null value. The SDK owns the JSON elements independently of
native memory and any source `JsonDocument`.

`PreparedPolicyRequest.ToJson` and `FromJson` use source-generated serialization
and validate shapes, required fields, operations, JSON Pointers, and limits.
Malformed data throws `JsonException` without echoing input or attaching parser
exceptions. Storage validation is not backend-policy validation or caller
acceptance. Native preparation failures set the `MxcException` `Code`,
`Operation`, `NativeCode`, and `Message` fields.

Preparation storage JSON uses compact escaping and is not intended for direct
embedding in HTML; escape it for HTML separately. Storage checks embedded
`RequestJson` syntax, object shape, encoding, and depth without normalizing its
text. The native engine validates the declared request contract, including
arbitrary `_comment` JSON. Duplicate members inside that annotation remain in
the request text; duplicate preparation-envelope properties are rejected.

| Preparation limit | Maximum |
|---|---:|
| Preparation input MXC request UTF-8 bytes | 8 MiB |
| Prepared result UTF-8 bytes, including the change report | 32 MiB |
| Changes | 4096 |

Preparation may expand the candidate request beyond the 8 MiB input bound.
The complete stored result, including its request JSON and changes, must fit
within 32 MiB.

The native operation owns remediation decisions and the stricter observation
input bounds listed below. The async method uses the same safe blocking-call
wrapper as captured execution. Its cancellation token comes last.

For example, after the application has saved observations from a refused call:

```csharp
using Microsoft.Mxc.Sdk.V1;
using Dev = Microsoft.Mxc.Sdk.V1.Dev;

Dev.PolicyFailures failures = Dev.PolicyFailures.FromJson(
await File.ReadAllTextAsync("policy-failures.json"));
Dev.PreparedPolicyRequest prepared = await Dev.MxcContainer.PreparePolicyJsonAsync(
requestJson, failures, cancellationToken: cancellationToken);
await File.WriteAllTextAsync("prepared-request.json", prepared.ToJson(), cancellationToken);

// The application must review and accept the changes before executing RequestJson.
Console.WriteLine("Review prepared-request.json. Type 'accept' to execute its request.");
if (Console.ReadLine() == "accept")
{
ExecutionResult result = await Dev.MxcContainer.RunJsonAsync(
prepared.RequestJson, cancellationToken: cancellationToken);
}
```

### Observations and storage

`FormatVersion` identifies the MXC storage format independently of native
`Version` and `MinimumReaderVersion`. Format version 1 is supported.
Native versions, numeric discriminators, outcomes, and flags retain unknown
`uint` values for inspection. Known native outcomes are 0 (unknown),
1 (blocked), and 2 (evaluation failed). `RequestedValue` describes the
request Windows evaluated, which need not be the request supplied on a
later call.

Use the public constant groups for comparisons, for example
`result.Outcome == PolicyFailureOutcomes.Blocked` or
`detail.FailureReason == PolicyFailureReasons.CapabilityNotAllowed`.
Properties remain `uint` or `ulong`, not closed enums; JSON keeps the numeric
encodings below and unknown values remain inspectable. Names do not authorize
replay.

`PolicyFailureNativeCodes.PolicyBlocked` is the `const string` `"0x800704EC"`
for `MxcException.NativeCode`:
`HRESULT_FROM_WIN32(ERROR_ACCESS_DISABLED_BY_POLICY)`. The status alone is not
a retry decision; also inspect the native operation,
structured outcome, progress, and application stop conditions.

| Static class | Public constants |
|---|---|
| `PolicyFailureOutcomes` (`uint`) | `Unknown = 0`, `Blocked = 1`, `EvaluationFailed = 2` |
| `PolicyFailureClasses` (`uint`) | `None = 0`, `Network = 1`, `Filesystem = 2`, `EnforcementMode = 3` |
| `PolicyFailureReasons` (`uint`) | `None = 0`, `CapabilityNotAllowed = 1`, `AccessExceedsCeiling = 2`, `DenyRequired = 3`, `RequestPathLimit = 4` |
| `PolicyFailureActions` (`uint`) | `None = 0`, `RemoveCapability = 1`, `RestrictFilesystemAccess = 2`, `ReducePathCount = 3` |
| `PolicyFailureResourceKinds` (`uint`) | `None = 0`, `Path = 1`, `Capability = 2` |
| `PolicyFailureValueKinds` (`uint`) | `None = 0`, `Access = 1`, `PathCount = 2` |
| `PolicyFailureAccessValues` (`ulong`) | `None = 0`, `ReadOnly = 1`, `ReadWrite = 2`; for requested/required values when `ValueKind` is `PolicyFailureValueKinds.Access` |
| `PolicyFailureFlags` (`uint`) | `None = 0`, `HasActionableDetails = 1`, `ResourceComplete = 2`, `ResourceUnavailable = 4`; combine or inspect as a bitmask |

`ToJson` and `FromJson` use source-generated serialization without reflection
and are supported by Native AOT. JSON field names are camelCase.
`requestedValue` and `requiredValue` are decimal strings, preserving the
entire `ulong` range. Other numeric fields are JSON numbers. Resource data
is an optional array of UTF-16 code units without a terminator. Omitted
resources, empty arrays, and malformed UTF-16 remain distinguishable.
JSON readers normalize `resource: null` to absence, and serialization omits it.
`GetResourceText()` returns null for an absent resource and throws
`DecoderFallbackException` for malformed UTF-16; it does not replace
invalid units, include resource bytes in decoder diagnostics, or establish
that the text is actionable.

`PolicyFailures.FromException(error)` extracts `error.Details.policyFailures`.
It returns null when the property is absent. If present observations are
invalid, contain unknown fields, use an unsupported format version, or exceed
storage limits, it throws a new `MxcException` with `Code == BackendError` and
the message `native policy failures are malformed`. `FromJson` reports these
problems in stored observations with `JsonException`. The original error and
its other details remain available. Validation diagnostics do not echo
supplied JSON or attach parser exceptions containing it. Native failures
preserve their original `Code`, `Operation`, `NativeCode`, and generic message.
Policy-specific resources, capabilities, and requested or required values
remain structured and are not converted into exception text, stacks, or
warnings.
Policy refusals retain high-level IT-managed-policy guidance.

Persist `PolicyFailures.ToJson()`, not the complete `Details` object, and pass
the restored value to preparation. Companion diagnostics and retry budgets
are application control state and need separate persistence if stopping
decisions must survive a restart. Parsing stored observations is not proof
that they can be used to prepare a request. Observation `formatVersion`,
native batch versions, and MXC request JSON `version` identify separate
contracts.

A creation refusal reports that attempt's observations, not observations passed
to an earlier preparation call. Use `prior.MergedWith(current)` to combine them.
The helper validates both storage inputs and appends distinct complete native
batches in first-observed order, preserving detail order and resource presence.
It returns owned arrays, including resource arrays, without changing either
input. A malformed input or a complete merge exceeding storage limits throws
`JsonException`; neither report is truncated. Keep the original reports
separately on that failure and stop rather than guessing which details to
discard.

| Observation limit | Native preparation input | JSON storage helpers |
|---|---:|---:|
| UTF-8 bytes | 4 MiB | 4.5 MiB |
| Results | 64 | 65 |
| Total details | 1024 | 1088 |
| Total resource accounting units | 262144 | 294912 |

Both paths allow at most 64 details and 32768 resource accounting units per
result. Each present resource counts its array length plus one unit.
Storage may preserve accumulated observations that exceed preparation input
bounds; successful storage does not imply that preparation accepts them.
Native validation owns the stricter input limits and all remediation decisions.

Successful creation returns the usual `ExecutionResult` or `MxcProcess`, with
the same warnings, stdin, cancellation, and ownership semantics. Observations
remain structured on errors.
8 changes: 8 additions & 0 deletions docs/api-reference/dotnet/v1/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,7 @@ public sealed class MxcException : Exception
public string? Operation { get; }
public string? NativeCode { get; }
public string? Remediation { get; }
public System.Text.Json.JsonElement? Details { get; }

public MxcException(ErrorCode code, string message)
: this(code, message, null, null, null);
Expand All @@ -595,6 +596,13 @@ public sealed class MxcException : Exception
}
```

`Details` preserves optional backend data independently of native memory and
the original JSON document. The
[V1.Dev observation helpers](dev.md#observations-and-storage)
provide typed extraction and Native AOT-compatible storage of
`Details.policyFailures`. Other backend details remain available through
the `JsonElement`; the exception message does not include them.


## `Microsoft.Mxc.Sdk.V1.MxcLifecycle`

Expand Down
2 changes: 1 addition & 1 deletion docs/api-reference/node/v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Filesystem discovery helpers and their result/options types use `policy.filesyst
Discovery, probes, telemetry, and policy helpers use the same versioned boundary.

- [Operation signatures](api.md)
- [V1.Dev exact-JSON API](https://github.com/microsoft/mxc/blob/main/docs/api-reference/node/v1/dev.md)
- [V1.Dev MXC request JSON API and policy preparation](https://github.com/microsoft/mxc/blob/main/docs/api-reference/node/v1/dev.md)
- [Types and fields](types.md)
- [Cross-SDK requirements](../../README.md)

Expand Down
2 changes: 1 addition & 1 deletion docs/api-reference/node/v1/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ rejects `guiAccess`.
Terminal handles give the caller explicit input, output, resize, and process
ownership; there is no separate attached-console launch API. Typed V1 does
not accept ["MXC request JSON"](https://github.com/microsoft/mxc/blob/main/docs/schema.md#mxc-request-json); the
[V1.Dev exact-JSON API](https://github.com/microsoft/mxc/blob/main/docs/api-reference/node/v1/dev.md)
[V1.Dev MXC request JSON API and policy preparation](https://github.com/microsoft/mxc/blob/main/docs/api-reference/node/v1/dev.md)
accepts caller-authored requests separately.
Captured existing-container execution returns partial text on timeout
with `timedOut: true`, without waiting for output streams to close.
Expand Down
Loading
Loading