From 2652e9077881a86e006b91f7581b700a194e94c2 Mon Sep 17 00:00:00 2001 From: Gudge Date: Tue, 13 Oct 2026 08:52:44 -0700 Subject: [PATCH] Align Node v1 conformance with the published 1.0 contract This PR aligns Node v1 one-shot type conformance with the published 1.0.0 contract emitted by the SDK. It removes the obsolete NetworkConfig type and Linux helper that mutated retired network fields. Runtime rejection of unsupported legacy policy remains intact. Details * Compare public v1 authoring types with generated published 1.0 wire types. * Use directional networking throughout the internal request mapping. * Remove obsolete legacy-network mutation and update codegen documentation. Tests * `npm run typecheck --prefix sdk\node` passed, including packed integration. * `npm test --prefix sdk\node` passed: 444 passed, 21 skipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 735d3e0b-8eff-4395-9f2c-dff9b0f86b74 Generated-with: gpt-6-sol --- .../build-and-test/schema-codegen.md | 4 +- sdk/node/src/bindings/one-shot.ts | 4 +- sdk/node/src/helper.ts | 61 ---------------- sdk/node/src/v1/container.ts | 7 +- sdk/node/src/v1/types.ts | 30 +------- sdk/node/tests/unit/wire-conformance.test.ts | 72 ++++++++----------- 6 files changed, 36 insertions(+), 142 deletions(-) diff --git a/docs/development/build-and-test/schema-codegen.md b/docs/development/build-and-test/schema-codegen.md index 66eef64cb..20cded92f 100644 --- a/docs/development/build-and-test/schema-codegen.md +++ b/docs/development/build-and-test/schema-codegen.md @@ -135,8 +135,8 @@ contract-valid, and no relaxed schema twin is generated. history and published registry identities. - `validate-configs.js` validates the repository config corpus against exact registered schemas discovered from the same registry command. -- `wire-conformance.test.ts` checks the Node one-shot public-to-raw v1.1 - mapping against the exact generated v1.1 oracle. +- `wire-conformance.test.ts` checks the Node one-shot public-to-raw v1.0 + mapping against the exact generated published v1.0 oracle. - `wire-conformance-state-aware.test.ts` checks backend-specific state-aware public types against the exact v0.9 and v1.1 oracles. - `check-schema-versions.js` and `check-version-sync.js` enforce schema and diff --git a/sdk/node/src/bindings/one-shot.ts b/sdk/node/src/bindings/one-shot.ts index 1ea1f64b8..afbb3bbe4 100644 --- a/sdk/node/src/bindings/one-shot.ts +++ b/sdk/node/src/bindings/one-shot.ts @@ -22,7 +22,7 @@ import { MxcError } from '../v1/errors.js'; import { SDK_CONTRACT_VERSION } from '../v1/contract-version.js'; import type { ContainerConfig, - NetworkConfig, + DirectionalNetworkConfig, PortMapping, ProcessContainerConfig, WslcConfig, @@ -114,7 +114,7 @@ function assertIntegerRange( } } -function validateNetworkPorts(network: NetworkConfig | undefined): void { +function validateNetworkPorts(network: DirectionalNetworkConfig | undefined): void { const egress = network?.egress; const rules = [...(egress?.allow ?? []), ...(egress?.deny ?? [])]; for (const [ruleIndex, rule] of rules.entries()) { diff --git a/sdk/node/src/helper.ts b/sdk/node/src/helper.ts index 4d7993fe4..5c6f45a18 100644 --- a/sdk/node/src/helper.ts +++ b/sdk/node/src/helper.ts @@ -102,67 +102,6 @@ export function makeLogFilePath(dir: string): string { return path.join(dir, `mxc-diag-${ts}-${suffix}.log`); } -/** - * Apply Linux network-policy defaults to a `ContainerConfig`. - * - * Linux enforces per-host filtering in one of two ways: - * 1. **iptables firewall** (`enforcementMode: 'firewall'`) — LXC's - * privileged enforcement path. Requires root / CAP_NET_ADMIN. - * 2. **Cooperative HTTP proxy** (`network.proxy` set) — Bubblewrap's - * unprivileged enforcement path. The proxy applies the host policy - * for cooperating HTTP clients; raw-socket clients bypass it. - * - * Legacy LXC network blocks with no explicit enforcement mode use `'firewall'` - * because capabilities are Windows-only. Bubblewrap uses firewall only for - * host lists without a proxy. - * - * If the caller explicitly passes `enforcementMode: 'capabilities'` we - * warn: `'capabilities'` is a Windows/AppContainer concept (a token - * capability mask) and has no Linux equivalent. The explicit value is left - * unchanged so native backend validation can report unsupported combinations. - * - * Shared between the explicit `'bubblewrap'` / `'lxc'` builders and the - * abstract `'process'` branch on Linux (which resolves to Bubblewrap - * server-side). - * - * NOTE: when `network.proxy` is configured on Bubblewrap, host filtering - * is enforced at the proxy layer (unprivileged, no CAP_NET_ADMIN). The - * Rust config parser explicitly rejects `bubblewrap + proxy + firewall` - * since the iptables path requires privilege the bwrap backend - * deliberately avoids. Callers in that mode must leave enforcementMode - * at its default. - */ -export function applyLinuxNetworkPolicy(config: ContainerConfig): void { - if (!config.network) { - return; - } - if (config.network.enforcementMode === 'capabilities') { - console.warn( - "mxc-sdk: network.enforcementMode='capabilities' has no effect on Linux " + - "(it is a Windows AppContainer / ProcessContainer concept). The Linux " + - "runner will not enforce host filtering via capabilities. Use the " + - "default mode (auto-promotes to 'firewall' for LXC, or use network.proxy " + - "for unprivileged Bubblewrap enforcement)." - ); - } - // LXC cannot enforce Windows AppContainer capabilities, so omitted legacy modes use firewall. - const isLegacyNetwork = - config.network.egress === undefined && config.network.ingress === undefined; - if ( - config.containment === 'lxc' && - isLegacyNetwork && - config.network.enforcementMode === undefined - ) { - config.network.enforcementMode = 'firewall'; - } - const hasProxy = !!config.network.proxy; - const hasHostRules = - !!(config.network.allowedHosts?.length || config.network.blockedHosts?.length); - if (hasHostRules && !hasProxy) { - config.network.enforcementMode = 'firewall'; - } -} - /** * Resolves the executor binary and builds the common CLI arguments for any * MXC request envelope (one-shot or state-aware). Performs platform support diff --git a/sdk/node/src/v1/container.ts b/sdk/node/src/v1/container.ts index 865bd2bfe..b9d9c18da 100644 --- a/sdk/node/src/v1/container.ts +++ b/sdk/node/src/v1/container.ts @@ -10,7 +10,6 @@ import { ExecutionResult, UnsupportedV1NetworkFields, } from './types.js'; -import { applyLinuxNetworkPolicy } from '../helper.js'; import { diagLog } from '../diagnostic.js'; import { MxcError } from './errors.js'; import { prepareOneShotRequest } from '../bindings/one-shot.js'; @@ -129,14 +128,13 @@ function buildWslcContainerConfig( /** * Builds the Bubblewrap (bwrap) portion of a ContainerConfig. * Bubblewrap is Linux-only and uses shared cross-backend fields only — - * no backend-specific config block. Network enforcement via iptables - * reuses the same approach as LXC. + * no backend-specific config block. Network policy uses shared directional + * fields enforced by the native backend. */ function buildBubblewrapConfig( config: ContainerConfig, ): ContainerConfig { config.containment = 'bubblewrap'; - applyLinuxNetworkPolicy(config); return config; } @@ -150,7 +148,6 @@ function buildLinuxProcessConfig( distribution: 'alpine', release: '3.23', }; - applyLinuxNetworkPolicy(config); return config; } diff --git a/sdk/node/src/v1/types.ts b/sdk/node/src/v1/types.ts index d8ba22dc6..b19af0127 100644 --- a/sdk/node/src/v1/types.ts +++ b/sdk/node/src/v1/types.ts @@ -251,34 +251,6 @@ export interface FilesystemConfig { clearPolicyOnExit?: boolean; } -/** - * Network access configuration. Legacy fields remain for TypeScript source - * compatibility only; every registered exact contract rejects them. Use - * directional fields and runtimeConfig.networkProxy instead. - */ -export interface NetworkConfig extends DirectionalNetworkConfig { - /** - * Retired enforcement selector; registered contracts reject this field. - * @deprecated Use directional network policy instead. - */ - enforcementMode?: 'capabilities' | 'firewall' | 'both'; - /** @deprecated Use egress.default instead; registered contracts reject this field. */ - defaultPolicy?: 'allow' | 'block'; - /** - * @deprecated Use ingress.default instead; registered contracts reject this field. - */ - allowLocalNetwork?: boolean; - /** @deprecated Use numeric CIDR egress rules or proxy-side hostname filtering. */ - allowedHosts?: string[]; - /** @deprecated Use numeric CIDR egress rules or proxy-side hostname filtering. */ - blockedHosts?: string[]; - /** @deprecated Registered contracts reject network.proxy. Use - * runtimeConfig.networkProxy with the backend's supported policy posture. */ - proxy?: { builtinTestServer: true } | { localhost: number } | { url: string }; - /** @deprecated Use lifecycle.preservePolicy; registered contracts reject this field. */ - removeRulesOnExit?: boolean; -} - /** Directional network policy used by provisioning. */ export interface DirectionalNetworkConfig { /** Outbound network policy. */ @@ -450,7 +422,7 @@ export interface ContainerConfig { /** Filesystem access configuration */ filesystem?: FilesystemConfig; /** Network access configuration */ - network?: NetworkConfig; + network?: DirectionalNetworkConfig; /** Runtime values supplied separately from sandbox policy. */ runtimeConfig?: NetworkRuntimeConfig; /** Telemetry configuration for TraceLogging ETW support */ diff --git a/sdk/node/tests/unit/wire-conformance.test.ts b/sdk/node/tests/unit/wire-conformance.test.ts index bc90c38f5..b53b27e47 100644 --- a/sdk/node/tests/unit/wire-conformance.test.ts +++ b/sdk/node/tests/unit/wire-conformance.test.ts @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// Exact v1.1 one-shot type conformance oracle. +// Exact v1.0 one-shot type conformance oracle. // -// The generated module `../../src/generated/v1_1_0_alpha/wire.ts` is emitted -// from the exact v1.1 contract. It is the source of truth for the raw one-shot -// shape produced by the current high-level SDK builder. +// The generated module `../../src/generated/v1_0_0/wire.ts` is emitted +// from the published v1.0 contract. It is the source of truth for the raw +// one-shot shape produced by the high-level V1 SDK builder. // // This file asserts — at COMPILE TIME — that the hand-written public SDK types -// in `../../src/types.ts` still conform to that generated shape. If the Rust +// in `../../src/v1/types.ts` still conform to that generated shape. If the Rust // exact contract changes (a field renamed/removed, an enum value added/dropped, // a type narrowed) the regenerated oracle shifts and these assertions stop // compiling, so `npm run build:test-unit` fails. The runtime body is a no-op; @@ -17,12 +17,11 @@ // Mapping rationale: // * The exact contract rejects null and requires one-shot `process`. The // high-level builder supplies the exact version, process, and LXC defaults; -// `PublicV1OneShotConfig` models that emitted raw shape. -// * `NetworkConfig` intentionally spans historical public versions. Its -// public-only key assertion documents the legacy fields omitted from v1.1 -// raw JSON while keeping directional field conformance exact. +// `PublicV1OneShotConfig` models that emitted raw shape after containment +// validation excludes development-only choices. +// * Network comparisons use the directional fields emitted by the V1 builder. // * Stable WSLC is additionally checked against the published v0.9 one-shot -// contract because this stack exposes WSLC in both v0.9 and v1.1. +// contract because this stack exposes WSLC in both v0.9 and v1.0. // * `OnlyInPublic` additionally catches a public field whose wire counterpart // was renamed or removed (width subtyping alone would not), and is asserted // to equal a documented, explicit set of SDK-only fields — so a NEW @@ -61,6 +60,7 @@ import type { import type { Containment as InternalContainment, } from '../../src/v1/types.js'; +import type { SDK_CONTRACT_VERSION } from '../../src/v1/contract-version.js'; import type { ContainerId as InternalContainerId, } from '../../src/v1/lifecycle-types.js'; @@ -85,7 +85,6 @@ import type { ProcessConfig, LifecycleConfig, FilesystemConfig, - NetworkConfig, DirectionalNetworkConfig, NetworkEgressConfig, NetworkIngressConfig, @@ -103,8 +102,6 @@ import type { TelemetryConfig, ContainerConfig, ClipboardPolicy as PublicClipboardPolicy, - ContainmentType, - ContainmentBackend, } from '../../src/v1/types.js'; import type { @@ -134,7 +131,7 @@ import type { ProcessContainerUiIsolation as WireUiIsolation, TransportProtocol as WireTransportProtocol, Version as WireVersion, -} from '../../src/generated/v1_1_0_alpha/wire.js'; +} from '../../src/generated/v1_0_0/wire.js'; import type { OneShotWslc as WireV09Wslc, @@ -151,6 +148,7 @@ import type { // --- enum / union conformance --------------------------------------------- +type _SdkContractVersion = AssertTrue>; type _V1BackendConfigExport = AssertTrue< Equivalent >; @@ -203,10 +201,9 @@ type _ExecutionResultExitCode = AssertTrue>; -// The SDK splits containment into abstract intents + concrete backends; their -// union must cover exactly the wire `Containment` enum. +// V1 creation supports exactly the containment choices in its published wire contract. type _Containment = AssertTrue< - Equivalent + Equivalent >; // Enum-backed object fields are checked bidirectionally so an exact-contract @@ -226,6 +223,9 @@ type _NetworkPortProtocol = AssertTrue< type _BaseProcessUiIsolation = AssertTrue< Equivalent, WireUiIsolation> >; +type _BaseProcessUiSystemSettings = AssertTrue< + Equivalent> +>; type _PortProtocol = AssertTrue< Equivalent, WireTransportProtocol> >; @@ -237,7 +237,7 @@ type _PortProtocol = AssertTrue< type _ProcessVals = AssertTrue>; type _LifecycleVals = AssertTrue>; type _FilesystemVals = AssertTrue>; -type _NetworkVals = AssertTrue>; +type _NetworkVals = AssertTrue>; type _NetworkEgressVals = AssertTrue>; type _NetworkIngressVals = AssertTrue>; type _NetworkPeerVals = AssertTrue>; @@ -280,21 +280,7 @@ type _TelemetryKeys = AssertTrue, 'clearPolicyOnExit'>>; -// The public network type spans historical contracts. Exact v1.1 emits only -// directional fields; the listed legacy and SDK-only fields are deliberately -// absent from its raw JSON mapping. -type _NetworkKeys = AssertTrue< - Equivalent< - OnlyInPublic, - | 'enforcementMode' - | 'defaultPolicy' - | 'allowLocalNetwork' - | 'allowedHosts' - | 'blockedHosts' - | 'proxy' - | 'removeRulesOnExit' - > ->; +type _NetworkKeys = AssertTrue, never>>; // `ProcessContainerConfig.name` is the deprecated AppContainer profile name // (superseded by top-level `containerId`); not a wire `processContainer` field. @@ -307,18 +293,20 @@ type _LxcKeys = AssertTrue, nev // --- ROOT conformance (review finding F1) --------------------------------- // Without these, a top-level wire field rename/removal regenerates wire.ts but // no assertion notices, so the leaf-only checks above are not enough. -// `PublicV1OneShotConfig` models the current high-level builder output: -// * its value shape must be assignable to exact v1.1 `OneShotRequest`; and +// `PublicV1OneShotConfig` models the current high-level builder output, +// excluding the development-only Hyperlight section of the public raw config: +// * its value shape must be assignable to exact v1.0 `OneShotRequest`; and // * it must have no public-only root keys. // A new root divergence fails the build. type PublicV1OneShotConfig = Omit< ContainerConfig, - 'version' | 'process' | 'network' | 'lxc' | 'appContainer' + 'version' | 'process' | 'network' | 'lxc' | 'appContainer' | 'containment' | 'hyperlight' > & { version: WireVersion; process: ProcessConfig; network?: DirectionalNetworkConfig; lxc?: RawV1LxcConfig; + containment?: V1Containment['type']; }; type _RootVals = AssertTrue>; type _RootKeys = AssertTrue< @@ -334,7 +322,7 @@ type _RootKeys = AssertTrue< type _ProcessWireKeys = AssertTrue, never>>; type _LifecycleWireKeys = AssertTrue, never>>; type _FilesystemWireKeys = AssertTrue, never>>; -type _NetworkWireKeys = AssertTrue, never>>; +type _NetworkWireKeys = AssertTrue, never>>; type _NetworkEgressWireKeys = AssertTrue, never>>; type _NetworkIngressWireKeys = AssertTrue, never>>; type _NetworkPeerWireKeys = AssertTrue, never>>; @@ -358,26 +346,24 @@ type _SeatbeltWireKeys = AssertTrue< >; type _TelemetryWireKeys = AssertTrue, never>>; -// Root: the high-level builder intentionally omits schema metadata, fallback, -// development-only test and Windows Sandbox sections. +// Root: the high-level builder intentionally omits schema metadata and fallback. type _RootWireKeys = AssertTrue< Equivalent< OnlyInWire, | '$schema' | '_comment' | 'fallback' - | 'test' - | 'windowsSandbox' > >; // Reference the assertion aliases so they read as intentionally load-bearing. export type WireConformanceAssertions = [ - _V1BackendConfigExport, _V1IdExport, + _SdkContractVersion, _V1BackendConfigExport, _V1IdExport, _ContainerRequestFilesystem, _ContainerRequestNetwork, _ContainerRequestContainment, _Clipboard, _Containment, _NetworkEgressDefault, _NetworkIngressDefault, _NetworkIngressHostLoopback, - _NetworkPortProtocol, _BaseProcessUiIsolation, _PortProtocol, + _NetworkPortProtocol, _BaseProcessUiIsolation, _BaseProcessUiSystemSettings, + _PortProtocol, _ProcessVals, _LifecycleVals, _FilesystemVals, _NetworkVals, _UiVals, _NetworkEgressVals, _NetworkIngressVals, _NetworkPeerVals, _NetworkPortVals, _NetworkRuleVals, _RuntimeConfigVals,