Skip to content

Enforce per-field schema version availability at parse time - #775

Open
Branden Bonaby (bbonaby) wants to merge 2 commits into
user/gudge/versioning_phase7d_dev_schema_gatefrom
user/gudge/versioning_phase8a_version_windows
Open

Enforce per-field schema version availability at parse time#775
Branden Bonaby (bbonaby) wants to merge 2 commits into
user/gudge/versioning_phase7d_dev_schema_gatefrom
user/gudge/versioning_phase8a_version_windows

Conversation

@bbonaby

@bbonaby Branden Bonaby (bbonaby) commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Important

This PR replaces #738 using the same phase8a implementation, with the original Gudge and Copilot attribution preserved in the feature commit. #738 was mistakenly rebased and pushed without its original phase8a changes while attempting to fix merge conflicts, so GitHub immediately marked the empty PR as merged in the original stack; this restores the full change and adds it back to the stack. PR description also taken from that PR.

📖 Description

Stacked on #732. Base is user/gudge/versioning_phase7d_dev_schema_gate; review the top commit only.

Summary

This PR adds per-field schema version availability: a wire field can declare the range of config schema versions it is valid in, and the parser rejects any use outside that range.

The governing requirement is that a breaking config-schema change must be possible without dropping support for an earlier version, where "support" means shape only — an older config keeps parsing and is enforced with today's semantics. #732 blocks the dev schema from accepting less than it did, which forces breaking changes to be additive; this PR supplies the other half. Without parse-time enforcement, an availability annotation is documentation that nothing honours.

Details

  • New mxc_version_derive proc-macro crate. #[derive(VersionAvailability)] lifts #[mxc_version(since = "0.8")] / until off wire.rs into metadata normal builds carry, so one declaration feeds both the parser and schema generation (published as x-mxc-since / x-mxc-until). It has to be a derive: #[schemars(extend(...))] sits behind the schema-gen feature, which only mxc_schema_gen enables, so it can annotate the schema but can never be consulted by the parser.
  • The mechanism fails open if a derived JSON name ever disagrees with what serde accepts — the range simply never fires — so that case is guarded twice. The macro compile-errors on every serde construct it cannot model exactly (flatten, split rename / rename_all, unrecognised rename_all rules, data-carrying variants, malformed literals), and a conformance test cross-checks all 32 wire types against the property names schemars derives independently from the same attributes.
  • Enforcement runs immediately after deserialisation in every entry point, because convert_wire_config moves fields out of the config and the document can no longer be checked as a whole. State-aware requests are gated on the original document, not the experimental-masked copy.
  • version is now required. It selects the legal field surface, so an absent one would silently opt out of every range rather than defaulting to something safe.
  • New version_incompatible error code across all five coupled surfaces (Rust MxcErrorCode, engine ErrorCode, TS, C#, MXC_STATUS_VERSION_INCOMPATIBLE = 13) carrying structured details: { field, declaredVersion, since, until }. The supported-range error migrates onto it, so one code covers both classes.
  • Three annotations, each checked against real corpus usage before landing: seatbelt since 0.7, processContainer.captureDenials and processContainer.learningMode since 0.8.
  • New check-version-availability.js oracle gate derives each field's true first appearance from the frozen 0.6/0.7 and dev schemas and fails when a declared since disagrees.
  • Corpus and callers migrated: 61 previously-unversioned configs, ~200 Rust test literals, the PowerShell lifecycle helpers (stamped centrally in each Invoke-StateAware* helper rather than at ~20 call sites), and the SDK config builders.

Tests

  • cargo fmt --all -- --check, cargo check --workspace --all-targets, cargo clippy --workspace --all-targets -- -D warnings, and the per-package test suites all clean on the rebased tip.
  • New corpus test asserts all 195 configs declare a version and still parse, with the out-of-range fixture pinned as a negative case by exact code and bounds.
  • Feature-gated builds covering every flag this diff can reach: wxc_common {schema-gen, microvm}, mxc_ffi {dotnetsdk}, mxc_engine {isolation_session}, wxc {isolation_session, microvm, tier2_bfs, wslc, hyperlight}.
  • Versioning gate suite 94 → 120 tests (26 of those arrive with Add fail-closed base resolution and SemVer libraries for the versioning gates #730's hardening). Node SDK build + 223 tests; C# SDK 35 tests; ErrorCode parity 17 codes; bindings codegen OK. All 11 CI gates pass.
  • Non-regression: the Bring network wire schema to full GA spec (wire.rs + config fixtures only) #676 replay still yields exactly 6 findings; detector baselines hold (dev vs dev = 0, 0.6→0.7 = 6, 0.7→dev = 12); Block breaking changes to the dev schema at pull-request time #732's gate passes; SUPPORTED_VERSION unchanged at >=0.6, <=0.8.
  • Converged through a 2-round adversarial review (14 findings: 12 fixed, 1 pushback accepted, 1 pre-existing and filed separately).

Notes for reviewers

Annotation is opt-in, and "unannotated" means no claim, not "since 0.6". An unannotated field is unbounded, which today is indistinguishable from since: 0.6, until: 0.8 because the supported-range check already rejects everything outside that window — but it becomes observable when the range moves, and unbounded is what lets fields keep working as a new dev line opens.

The most important part of this PR is what is deliberately not annotated. A field's first appearance in the JSON Schema is only a lower bound on how long it has been accepted:

  • experimental declared no properties before 0.8, so anything under it validated vacuously and has always been accepted.
  • State-aware requests declare 0.6.0-alpha while carrying phase / sandboxId / correlationVector, which the schema only described from 0.8.

Deriving bounds from schema data alone would have approved since: 0.8 on phase and rejected every state-aware request ever sent. Measured: 66 properties are unannotated yet absent from the 0.6 schema — 8 are covered transitively by an annotated ancestor (the walker checks a field's range before descending, so a rejected parent is never traversed into), and the rest legitimately carry none. The oracle gate is therefore fail-closed on those surfaces: it refuses a declaration it cannot justify rather than checking it against a bound that would be wrong. corpus_parses.rs is the behavioural counter-check the oracle structurally cannot provide.

Put the range on the containing field, never inside a shared struct. Seatbelt is reachable from both the top-level seatbelt section and experimental.seatbelt — it is one node, so a range on its inner fields would leak onto the unconstrained experimental surface. The oracle gate catches this class automatically.

One deliberate observable break: migrating the supported-range error onto version_incompatible changes an existing error's shape, so a consumer string-matching the old "older/newer than supported" message is affected. This was flagged and accepted in review.

Not executed on this host (Windows): the macOS Seatbelt paths, the Windows Sandbox and IsolationSession PowerShell lifecycle suites, and the host-gated MicroVM / Hyperlight E2E configs. The macOS code does cross-compile — cargo check --target aarch64-apple-darwin --all-targets is clean for mxc_engine, wxc_common and mxc-sdk, including the new cfg(target_os = "macos") regression tests — but it has not been run. mxc_darwin cannot be cross-checked at all, for the pre-existing reason in #735.

Microsoft Reviewers: Open in CodeFlow

🔗 References

🔍 Validation

  • cargo fmt --manifest-path src/Cargo.toml --all -- --check
  • cargo test --manifest-path src/Cargo.toml -p mxc_version_derive -p wxc_common -p mxc_engine
  • npm test --prefix scripts/versioning
  • Schema, SDK wire-type, and version-availability codegen gates

✅ Checklist

  • Signed the Contributor License Agreement
  • Linked to an issue
  • Updated documentation (if applicable)
  • Updated Copilot instructions (if build, architecture, or conventions changed)
  • If this PR changes Cargo.lock, the dependency-feed-check check passes (pending CI)

📋 Issue Type

  • Bug fix
  • Feature
  • Task
Microsoft Reviewers: Open in CodeFlow

@azure-pipelines

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

@bbonaby
Branden Bonaby (bbonaby) force-pushed the user/gudge/versioning_phase8a_version_windows branch from 086ccca to fa77fd7 Compare August 7, 2026 19:26

@bbonaby Branden Bonaby (bbonaby) left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reposting Soham Das (@SohamDas2021)'s review feedback inline for continuity from accidentally merged #738.

const firstAppearance = (path) => timeline.find((t) => t.paths.has(path));
const atLabel = (label) => timeline.find((t) => t.label === label);

for (const record of declared) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Originally posted by Soham Das (@SohamDas2021) on #738 (original comment):

checkAvailability only iterates declared (records that already carry an x-mxc-since / x-mxc-until). A brand-new field added after the floor with no annotation produces no record, so it is never checked?

);
} else {
for (const path of record.paths) {
if (!at.paths.has(path)) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Originally posted by Soham Das (@SohamDas2021) on #738 (original comment):

This only confirms the field exists at the until version; it never checks that the field is absent from every later frozen schema. A field still present in a frozen 0.7 schema can declare x-mxc-until:"0.6" and pass here after which the runtime wrongly rejects valid 0.7 configs that use it?

throw new Error(`schema-version.json: 'min' (${schemaVer.min}) is not a version`);
}

const stable = readdirSync(stableDir)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Originally posted by Soham Das (@SohamDas2021) on #738 (original comment):

discoverTimeline reads schemas/stable/* from the HEAD worktree (readdirSync + readJson off disk), so a PR that edits or adds a stable schema shifts the very timeline the since / until claims are validated against. This is asymmetric with #732/#730, which read the comparison baseline from the base commit via git-base?

"field": "version",
"declaredVersion": "",
"since": null,
"until": null,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Originally posted by Soham Das (@SohamDas2021) on #738 (original comment):

nit: for a missing/empty version, the SDK path emits "since": null, "until": null here, while the parser path emits since: MIN_SUPPORTED / until: MAX_SUPPORTED for the equivalent error in config_parser.rs?

Comment on lines 87 to 89
: ` (dev line moved ${baseVersions.devSchemaFile} -> ${headVersions.devSchemaFile})`;

const findings = detectBreaking(baseSchema, headSchema);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Originally posted by Soham Das (@SohamDas2021) on #738 (original comment):

This gate advertises a floor-bump escape hatch it doesn't implement. detectBreaking(baseSchema, headSchema) compares the complete schemas; neither baseVersions.min nor headVersions.min scopes it, so a removal is blocked unconditionally- even in a PR that raises the floor.
Fixes:
• A (now): make the message honest- drop "or move the supported-availability range in the same change" from L98 and correct the L17–20 comment to say removals are always blocked and window-based retirement isn't implemented yet.
• B (real fix): when headVersions.min > baseVersions.min , scope the comparison to surface still supported after the new floor — prune base nodes whose x-mxc-until < headMin before  detectBreaking (watch $ref -shared definitions so a retired field sharing a type with a live one doesn't over-prune), plus the integration test that raises min and removes retired surface. Note there's no x-mxc-until surface today, so B needs a synthetic fixture to exercise it.

@bbonaby
Branden Bonaby (bbonaby) marked this pull request as ready for review August 7, 2026 19:46
@bbonaby
Branden Bonaby (bbonaby) requested review from a team and a balanced review from Copilot August 7, 2026 19:46
@bbonaby
Branden Bonaby (bbonaby) requested a review from a team as a code owner August 7, 2026 19:46
@azure-pipelines

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds parse-time schema-version availability enforcement, generated metadata, typed errors, compatibility gates, and required-version migrations.

Changes:

  • Introduces VersionAvailability derive and runtime validation.
  • Publishes and validates x-mxc-since/x-mxc-until.
  • Migrates SDKs, tests, scripts, and fixtures to required versions.
Show a summary per file
File Description
.github/copilot-instructions.md Documents availability conventions.
.github/workflows/Versioning.Checks.Job.yml Runs the new oracle gate.
docs/schema.md Documents required versions and ranges.
docs/state-aware-lifecycle/mxc-state-aware-sandbox-api-overview.md Adds the version error category.
docs/state-aware-lifecycle/mxc-state-aware-sandbox-api.md Documents state-aware version handling.
docs/versioning.md Defines availability design and gates.
schemas/dev/mxc-config.schema.0.8.0-dev.json Publishes availability annotations.
scripts/versioning/check-dev-schema-compat.js Updates compatibility terminology.
scripts/versioning/check-version-availability.js Adds the schema-history oracle.
scripts/versioning/lib/version-availability.js Implements oracle traversal and checks.
scripts/versioning/package.json Registers the oracle command.
scripts/versioning/tests/version-availability.test.js Tests oracle behavior.
sdk/dotnet/Microsoft.Mxc.Sdk.Tests/MxcSandboxProcessTests.cs Updates streaming error expectations.
sdk/dotnet/Microsoft.Mxc.Sdk.Tests/MxcSandboxTests.cs Updates run error expectations.
sdk/dotnet/Microsoft.Mxc.Sdk/ErrorCode.cs Adds VersionIncompatible.
sdk/node/src/errors.ts Adds the TypeScript error code.
sdk/node/src/generated/wire.ts Regenerates wire documentation.
sdk/node/src/sandbox.ts Avoids invalid Seatbelt marker emission.
sdk/node/tests/unit/sandbox.test.ts Tests Seatbelt version handling.
src/Cargo.lock Locks the derive crate.
src/Cargo.toml Adds proc-macro workspace dependencies.
src/core/mxc-sdk/README.md Versions state-aware examples.
src/core/mxc-sdk/src/lib.rs Initializes structured error details.
src/core/mxc-sdk/tests/sandbox.rs Tests typed version failures.
src/core/mxc-sdk/tests/sdk_helpers.rs Updates empty-version expectations.
src/core/mxc-sdk/tests/state_aware.rs Versions lifecycle requests.
src/core/mxc_engine/src/error.rs Exposes typed error details.
src/core/mxc_engine/src/policy.rs Preserves version errors and handles Seatbelt.
src/core/mxc_engine/src/state_aware.rs Preserves parse error details.
src/core/mxc_version_derive/Cargo.toml Defines the proc-macro crate.
src/core/mxc_version_derive/src/lib.rs Implements the availability derive.
src/core/wxc/src/main.rs Versions CLI test policies.
src/core/wxc_common/Cargo.toml Adds the derive dependency.
src/core/wxc_common/src/config_deserialize.rs Borrows values during deserialization.
src/core/wxc_common/src/config_parser.rs Enforces required versions and ranges.
src/core/wxc_common/src/error.rs Adds structured version errors.
src/core/wxc_common/src/lib.rs Exports availability support.
src/core/wxc_common/src/mxc_error.rs Adds the wire error code.
src/core/wxc_common/src/telemetry/mod.rs Classifies version failures.
src/core/wxc_common/src/version_availability.rs Implements metadata traversal and validation.
src/core/wxc_common/src/wire.rs Derives and declares field ranges.
src/core/wxc_common/tests/corpus_parses.rs Validates the configuration corpus.
src/ffi/mxc_ffi/src/lib.rs Adds the FFI status code.
src/ffi/mxc_ffi/src/state_aware.rs Versions FFI lifecycle tests.
src/testing/wxc_e2e_tests/tests/e2e_state_aware.rs Versions state-aware E2E requests.
src/testing/wxc_e2e_tests/tests/e2e_windows.rs Versions Windows E2E requests.
src/tools/mxc_schema_gen/Cargo.toml Adds schema-test JSON support.
src/tools/mxc_schema_gen/tests/version_availability_conformance.rs Cross-checks schema metadata.
tests/configs/hyperlight_exit_code.json Adds required version.
tests/configs/hyperlight_fs.json Adds required version.
tests/configs/hyperlight_hello.json Adds required version.
tests/configs/hyperlight_networking.json Adds required version.
tests/configs/hyperlight_networking_blocked.json Adds required version.
tests/configs/hyperlight_pandas.json Adds required version.
tests/configs/hyperlight_timeout.json Adds required version.
tests/configs/isolation_session_state_aware_deprovision.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_basic.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_cwd.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_env_absent.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_env_initial.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_env_modified.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_exit_0.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_exit_1.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_exit_2.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_read_marker.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_read_persist.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_read_readonly.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_read_restricted.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_read_shared.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_setx_initial.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_setx_modified.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_write_marker.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_write_readonly_denied.json Adds state-aware version.
tests/configs/isolation_session_state_aware_exec_write_shared.json Adds state-aware version.
tests/configs/isolation_session_state_aware_provision.json Adds state-aware version.
tests/configs/isolation_session_state_aware_provision_rejected_denied.json Adds state-aware version.
tests/configs/isolation_session_state_aware_provision_user_empty_wamtoken.json Adds state-aware version.
tests/configs/isolation_session_state_aware_provision_user_malformed_upn.json Adds state-aware version.
tests/configs/isolation_session_state_aware_provision_with_filesystem.json Adds state-aware version.
tests/configs/isolation_session_state_aware_provision_with_filter.json Adds state-aware version.
tests/configs/isolation_session_state_aware_start.json Adds state-aware version.
tests/configs/isolation_session_state_aware_start_entra_missing_user.json Adds state-aware version.
tests/configs/isolation_session_state_aware_start_local_with_user.json Adds state-aware version.
tests/configs/isolation_session_state_aware_start_medium.json Adds state-aware version.
tests/configs/isolation_session_state_aware_start_upn_mismatch.json Adds state-aware version.
tests/configs/isolation_session_state_aware_stop.json Adds state-aware version.
tests/configs/microvm_error.json Adds required version.
tests/configs/microvm_error_linux.json Adds required version.
tests/configs/microvm_exit_code.json Adds required version.
tests/configs/microvm_exit_code_linux.json Adds required version.
tests/configs/microvm_hello.json Adds required version.
tests/configs/microvm_hello_linux.json Adds required version.
tests/configs/microvm_large_output.json Adds required version.
tests/configs/microvm_large_output_linux.json Adds required version.
tests/configs/microvm_multiline.json Adds required version.
tests/configs/microvm_multiline_linux.json Adds required version.
tests/configs/microvm_network.json Adds required version.
tests/configs/microvm_network_linux.json Adds required version.
tests/configs/microvm_stdlib.json Adds required version.
tests/configs/microvm_stdlib_linux.json Adds required version.
tests/configs/microvm_timeout.json Adds required version.
tests/configs/microvm_timeout_linux.json Adds required version.
tests/configs/windows_sandbox_echo.json Adds required version.
tests/configs/windows_sandbox_exit_code.json Adds required version.
tests/configs/windows_sandbox_powershell.json Adds required version.
tests/configs/windows_sandbox_powershell_env.json Adds required version.
tests/configs/windows_sandbox_stderr.json Adds required version.
tests/configs/windows_sandbox_timeout.json Adds required version.
tests/examples/28_telemetry_enabled.json Adds required version.
tests/scripts/run_isolation_session_state_aware_tests.ps1 Stamps state-aware request versions.
tests/scripts/run_isolation_session_tests.ps1 Stamps probe request versions.
tests/scripts/run_windows_sandbox_state_aware_tests.ps1 Stamps state-aware request versions.

Review details

  • Files reviewed: 109/112 changed files
  • Comments generated: 7
  • Review effort level: Balanced

Comment on lines +51 to +53
fn expand(input: DeriveInput) -> Result<TokenStream2> {
let ident = &input.ident;
let type_name = ident.to_string();
Comment on lines +33 to +38
fn collect(dir: &Path, root: &Path, out: &mut Vec<(String, PathBuf)>) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
Comment on lines +838 to +844
return Err(MxcError::version_incompatible("Policy version is required")
.with_details(serde_json::json!({
"field": "version",
"declaredVersion": "",
"since": null,
"until": null,
}))
Comment thread sdk/node/src/errors.ts
Comment on lines +24 to +31
/**
* The config declared an unsupported schema version, or used a field outside
* the version window it is valid in. `details` carries
* `{ field, declaredVersion, since, until }` — `field` is the dotted path of
* the offending field, or `"version"` when the declared version itself is
* outside the supported range.
*/
| 'version_incompatible';
ErrorCode::AlreadyStopped => MXC_STATUS_ALREADY_STOPPED,
ErrorCode::PolicyValidation => MXC_STATUS_POLICY_VALIDATION,
ErrorCode::BackendError => MXC_STATUS_BACKEND_ERROR,
ErrorCode::VersionIncompatible => MXC_STATUS_VERSION_INCOMPATIBLE,
Comment on lines +151 to +155
if (record.paths.length === 0) {
errors.push(
`${name}: declares a availability range but is not reachable from the schema root, ` +
`so no config could ever use it`
);
Comment on lines +165 to +173
if (underivable.length > 0) {
const { path, root } = underivable[0];
errors.push(
`${name}: a availability range cannot be declared at '${underivable
.map((e) => e.path)
.join("', '")}'. The '${root.path}' surface ${root.why}, so its first appearance ` +
`is not derivable from the schemas — a bound taken from schema presence would start ` +
`rejecting configs that have always worked. If this availability range is on a shared type, move ` +
`it to the containing field instead.`
@jsidewhite

Copy link
Copy Markdown
Member

I still think we can just nuke all parser code and use a standard json schema validator.

Gudge (MGudgin) and others added 2 commits August 7, 2026 15:33
This PR adds per-field schema version availability: a wire field can declare the
range of config schema versions it is valid in, and the parser rejects any use
outside that range. It is what makes shape-only support for older schema
versions real — until now such an annotation would have been documentation that
nothing honoured.

Details

* New `mxc_version_derive` proc-macro crate. `#[derive(VersionAvailability)]`
  lifts `#[mxc_version(since = "0.8")]` / `until` off `wire.rs` into metadata
  normal builds carry, so one declaration feeds both the parser and schema
  generation (published as `x-mxc-since` / `x-mxc-until`). A derive rather than
  `#[schemars(extend(...))]`, which sits behind the `schema-gen` feature and so
  can never be consulted by the parser.
* The mechanism fails **open** if a derived JSON name ever disagrees with what
  serde accepts — the range simply never fires — so that case is guarded twice:
  the macro compile-errors on every serde construct it cannot model exactly
  (`flatten`, split `rename`/`rename_all`, unknown `rename_all` rules,
  data-carrying variants), and a conformance test cross-checks all 32 wire types
  against the names `schemars` independently derives.
* The gate runs immediately after deserialisation in every entry point, because
  `convert_wire_config` moves fields out of the config; state-aware requests are
  gated on the original document, not the experimental-masked copy.
* `version` is now **required** — it selects the legal field surface, so an
  absent one would silently opt out of every range.
* New `version_incompatible` code across all five surfaces (Rust `MxcErrorCode`,
  engine `ErrorCode`, TS, C#, `MXC_STATUS_VERSION_INCOMPATIBLE = 13`) carrying
  `details: { field, declaredVersion, since, until }`. The supported-range error
  migrates onto it. NOTE: this changes an existing error's observable shape — a
  consumer string-matching the old range message is affected.
* Three annotations, each checked against real corpus usage first: `seatbelt`
  since 0.7, `processContainer.captureDenials` / `learningMode` since 0.8. The
  central subtlety is what is deliberately **not** annotated: schema-first-
  appearance is only a lower bound on accepted surface. `experimental` was an
  open block before 0.8, and state-aware requests declare 0.6 while carrying
  `phase` / `sandboxId` / `correlationVector` — annotating those from schema
  data would reject configs that have always worked. (Measured: 66 properties
  are unannotated yet absent from the 0.6 schema; 8 are covered transitively by
  an annotated ancestor, and the rest legitimately carry no range.)
* New `check-version-availability.js` oracle gate derives each field's true
  first appearance from the frozen 0.6/0.7 and dev schemas and fails on
  disagreement. It is fail-closed on the surfaces above, which also catches a
  range that would leak onto the permissive `experimental` surface via a shared
  type.
* Corpus and callers migrated: 61 configs versioned (state-aware to 0.6.0-alpha,
  matching what the SDK emits; one-shot to 0.8.0-alpha), ~200 Rust test
  literals, the PowerShell lifecycle helpers (stamped centrally), and the SDK
  builders, which no longer synthesise a top-level `seatbelt` marker below 0.7.

Tests

* On the final tip: `cargo fmt --all -- --check`,
  `cargo check --workspace --all-targets`,
  `cargo clippy --workspace --all-targets -- -D warnings`, and the per-package
  test suites (`wxc_common` incl. the corpus test, `mxc_schema_gen`,
  `mxc_version_derive`, `mxc_engine`, `mxc-sdk`, `mxc_ffi`, `wxc`,
  `wxc_e2e_tests::e2e_state_aware`) all clean.
* Feature-gated builds covering every flag this diff can reach, all clean:
  `wxc_common` {schema-gen, microvm}, `mxc_ffi` {dotnetsdk}, `mxc_engine`
  {isolation_session}, `wxc` {isolation_session, microvm, tier2_bfs, wslc,
  hyperlight}.
* `wxc_common` 594 unit tests plus a new corpus test asserting all 195 configs
  declare a version and still parse, with the out-of-range fixture pinned as a
  negative case by exact code and bounds. Versioning gate tests 71 → 94.
* Node SDK build + 223 tests; C# SDK 35 tests; ErrorCode parity 17 codes;
  bindings codegen OK. All 11 CI gates pass.
* Non-regression: the PR #676 replay still yields exactly 6 findings naming
  `allowLocalNetwork`, `allowedHosts`, `blockedHosts`, `defaultPolicy`,
  `enforcementMode`, `proxy`; detector baselines hold (dev vs dev = 0,
  0.6→0.7 = 6, 0.7→dev = 12); the dev-schema gate passes; `SUPPORTED_VERSION`
  unchanged at `>=0.6, <=0.8`.
* Converged through a 2-round adversarial review (14 findings; 12 fixed, 1
  pushback accepted, 1 pre-existing). Two blockers were genuine test failures an
  earlier verification pass had masked with a faulty grep.
* **Not executed on this host** (Windows): the macOS Seatbelt paths, the Windows
  Sandbox and IsolationSession PowerShell lifecycle suites, and the host-gated
  MicroVM / Hyperlight E2E configs. The macOS code does **cross-compile** —
  `cargo check --target aarch64-apple-darwin --all-targets` is clean for
  `mxc_engine`, `wxc_common` and `mxc-sdk`, including the new
  `cfg(target_os = "macos")` regression tests — but it has not been run.
  (`mxc_darwin` cannot be cross-checked at all: pre-existing issue #735.)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 21bb36ae-131a-4ab6-b062-a830ba488428
Generated-with: claude-opus-5
Regenerate the SDK wire types with the current emitter and update tests added below phase8a to use the newly required schema version and availability bounds.

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

Copilot-Session: 43f038b4-b943-4a48-965f-ed8350b7a30a
Copilot AI review requested due to automatic review settings August 7, 2026 22:34
@shschaefer
shschaefer force-pushed the user/gudge/versioning_phase8a_version_windows branch from fa77fd7 to 309bc30 Compare August 7, 2026 22:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review details

Suppressed comments (3)

sdk/node/src/errors.ts:31

  • The one-shot TypeScript SDK's validatePolicyVersion still throws a plain Error for missing, too-old, and too-new versions, so those public entry points do not expose this new version_incompatible code or its structured details. Only errors that reach an executor envelope can use this union member. Make local version validation throw MxcError with the same details contract (or remove the duplicate validation and consistently use the executor result).
  /**
   * The config declared an unsupported schema version, or used a field outside
   * the version window it is valid in. `details` carries
   * `{ field, declaredVersion, since, until }` — `field` is the dotted path of
   * the offending field, or `"version"` when the declared version itself is
   * outside the supported range.
   */
  | 'version_incompatible';

src/core/mxc_engine/src/policy.rs:843

  • This early SDK-only path returns since: null / until: null, while the shared parser returns the supported bounds (0.6/0.8) for the same missing/empty version and the documented contract says version-field failures carry those bounds. Callers therefore see different structured details depending on whether they entered through build_request or raw JSON. Reuse the parser's version diagnostic, or source the bounds from the same canonical constants rather than constructing a divergent error here.
                "since": null,
                "until": null,

src/ffi/mxc_ffi/src/lib.rs:117

  • This adds the numeric status, but all FFI adapters still discard Error.details when converting failures to (status, message)/error_utf8 (mxc_run, streaming spawn, state-aware envelope, and state-aware exec), and MxcException exposes no details property. Consequently C and C# callers cannot access the advertised { field, declaredVersion, since, until } contract. Extend the FFI error results with details JSON and surface it on MxcException, or narrow the documented cross-surface contract.
  • Files reviewed: 109/112 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment on lines +430 to +431
/// Flattens every `#[serde(...)]` attribute into its entries. Parsing into
/// [`Meta`] lets unrecognised serde options be consumed and ignored.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants