Enforce per-field schema version availability at parse time - #775
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
086ccca to
fa77fd7
Compare
Branden Bonaby (bbonaby)
left a comment
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
| : ` (dev line moved ${baseVersions.devSchemaFile} -> ${headVersions.devSchemaFile})`; | ||
|
|
||
| const findings = detectBreaking(baseSchema, headSchema); |
There was a problem hiding this comment.
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.
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Adds parse-time schema-version availability enforcement, generated metadata, typed errors, compatibility gates, and required-version migrations.
Changes:
- Introduces
VersionAvailabilityderive 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
| fn expand(input: DeriveInput) -> Result<TokenStream2> { | ||
| let ident = &input.ident; | ||
| let type_name = ident.to_string(); |
| 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(); |
| return Err(MxcError::version_incompatible("Policy version is required") | ||
| .with_details(serde_json::json!({ | ||
| "field": "version", | ||
| "declaredVersion": "", | ||
| "since": null, | ||
| "until": null, | ||
| })) |
| /** | ||
| * 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, |
| 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` | ||
| ); |
| 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.` |
|
I still think we can just nuke all parser code and use a standard json schema validator. |
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
fa77fd7 to
309bc30
Compare
There was a problem hiding this comment.
Review details
Suppressed comments (3)
sdk/node/src/errors.ts:31
- The one-shot TypeScript SDK's
validatePolicyVersionstill throws a plainErrorfor missing, too-old, and too-new versions, so those public entry points do not expose this newversion_incompatiblecode or its structured details. Only errors that reach an executor envelope can use this union member. Make local version validation throwMxcErrorwith 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 throughbuild_requestor 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.detailswhen converting failures to(status, message)/error_utf8(mxc_run, streaming spawn, state-aware envelope, and state-aware exec), andMxcExceptionexposes 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 onMxcException, or narrow the documented cross-surface contract.
- Files reviewed: 109/112 changed files
- Comments generated: 1
- Review effort level: Balanced
| /// Flattens every `#[serde(...)]` attribute into its entries. Parsing into | ||
| /// [`Meta`] lets unrecognised serde options be consumed and ignored. |
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
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
mxc_version_deriveproc-macro crate.#[derive(VersionAvailability)]lifts#[mxc_version(since = "0.8")]/untiloffwire.rsinto metadata normal builds carry, so one declaration feeds both the parser and schema generation (published asx-mxc-since/x-mxc-until). It has to be a derive:#[schemars(extend(...))]sits behind theschema-genfeature, which onlymxc_schema_genenables, so it can annotate the schema but can never be consulted by the parser.flatten, splitrename/rename_all, unrecognisedrename_allrules, data-carrying variants, malformed literals), and a conformance test cross-checks all 32 wire types against the property namesschemarsderives independently from the same attributes.convert_wire_configmoves 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.versionis 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.version_incompatibleerror code across all five coupled surfaces (RustMxcErrorCode, engineErrorCode, TS, C#,MXC_STATUS_VERSION_INCOMPATIBLE = 13) carrying structureddetails: { field, declaredVersion, since, until }. The supported-range error migrates onto it, so one code covers both classes.seatbeltsince 0.7,processContainer.captureDenialsandprocessContainer.learningModesince 0.8.check-version-availability.jsoracle gate derives each field's true first appearance from the frozen 0.6/0.7 and dev schemas and fails when a declaredsincedisagrees.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.wxc_common{schema-gen, microvm},mxc_ffi{dotnetsdk},mxc_engine{isolation_session},wxc{isolation_session, microvm, tier2_bfs, wslc, hyperlight}.SUPPORTED_VERSIONunchanged at>=0.6, <=0.8.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.8because 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:
experimentaldeclared no properties before 0.8, so anything under it validated vacuously and has always been accepted.0.6.0-alphawhile carryingphase/sandboxId/correlationVector, which the schema only described from 0.8.Deriving bounds from schema data alone would have approved
since: 0.8onphaseand 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.rsis the behavioural counter-check the oracle structurally cannot provide.Put the range on the containing field, never inside a shared struct.
Seatbeltis reachable from both the top-levelseatbeltsection andexperimental.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_incompatiblechanges 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-targetsis clean formxc_engine,wxc_commonandmxc-sdk, including the newcfg(target_os = "macos")regression tests — but it has not been run.mxc_darwincannot 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 -- --checkcargo test --manifest-path src/Cargo.toml -p mxc_version_derive -p wxc_common -p mxc_enginenpm test --prefix scripts/versioning✅ Checklist
Cargo.lock, thedependency-feed-checkcheck passes (pending CI)📋 Issue Type
Microsoft Reviewers: Open in CodeFlow