Merge nucleic/olive-jade-civet-rznt into dev

This commit is contained in:
2026-07-29 00:14:32 -07:00
parent b342860779
commit 140b358da6
6 changed files with 127 additions and 80 deletions
+8 -3
View File
@@ -16,7 +16,10 @@
</PropertyGroup>
<PropertyGroup Condition="'$(UseWslc)' == 'true'">
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<!-- Windows SDK 19041, matching the package's own lib TFM
(lib/net8.0-windows10.0.19041.0/wslcsdkcs.dll). Targeting a higher SDK revision
(26100) only adds a targeting-pack requirement the package does not need. -->
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
<DefineConstants>$(DefineConstants);USE_WSLC</DefineConstants>
</PropertyGroup>
@@ -26,8 +29,10 @@
<ItemGroup Condition="'$(UseWslc)' == 'true'">
<!-- Version-pinned (docs/WINDOWS_PORT.md §15): bump deliberately per release, never
float — the API is preview and breaking changes must be absorbed behind IWslc. -->
<PackageReference Include="Microsoft.WSL.Containers" Version="[0.1.0-preview.1]" />
float — the API is preview and breaking changes must be absorbed behind IWslc.
2.9.3 is the ONLY version on nuget.org and it tracks WSL's own version scheme, not
a `0.1.0-preview.N` one; the invented pin this replaced could never have restored. -->
<PackageReference Include="Microsoft.WSL.Containers" Version="[2.9.3]" />
</ItemGroup>
</Project>
+23 -4
View File
@@ -4,10 +4,29 @@ using Microsoft.WSL.Containers;
namespace NucleicBroker.Wslc;
// The REAL Microsoft.WSL.Containers adapter (docs/WINDOWS_PORT.md §1.5, §3.2), compiled
// only with -p:UseWslc=true on Windows. The API is public preview (GA fall 2026) and its
// exact shapes are validated by the M1 wslc spike — every mapping below that spikes prove
// wrong gets fixed HERE, never above the IWslc seam. Until M1 runs on real hardware, treat
// this file as the best-effort transcription of the documented object model:
// only with -p:UseWslc=true on Windows.
//
// !! KNOWN WRONG AS WRITTEN — DO NOT BUILD ON IT. !!
//
// M1 spike (a) has run (docs/WINDOWS_PORT.md §13.1) and the real API differs from this
// transcription in roughly twenty places. Most are renames that belong exactly here and
// nowhere else, which is what the IWslc seam is for: GetVersion not GetServiceVersion,
// `new Session(settings)` + Start() not CreateOrOpen, MemorySizeInMB, HostName, ImageName,
// CreateProcess-then-Start rather than RunProcess, DeleteContainerOption, a named Signal
// enum, RegistryAuth as a string, ImageInfo.Name/.Sha256, and two separate output events
// instead of one with a stderr flag.
//
// Three differences are NOT renames and need decisions before this file is finished:
// 1. there is no container enumeration at all, so §2.3 broker reattach and the
// container.list RPC have no API behind them;
// 2. there is no per-container statistics call, so container.stats must exec cgroup
// reads inside the container instead;
// 3. there is no pty and no uid/gid on ProcessSettings, so §7's Terminal panel loses
// tty mode and exec wraps argv in setpriv/su (which §3.2 already anticipated).
//
// Read §13.1 before touching this file. Fixing it is the next step of item 5, and it is
// now a fast loop: the package restores, so `dotnet build -p:UseWslc=true` compiles it.
//
// WslcService (components) → Session (VM host, images) → Container → Process.
public sealed class WslcFacade : IWslc
{
+18 -9
View File
@@ -11,15 +11,23 @@ NuGet and a real WSL stack, so they are built by path.
## `WslcApiDump` — what does the wslc API actually look like?
**The problem it solves.** Items 5, 6 and 7 — the C# broker, the Swift wslc client, and the
control plane — were all built against Microsoft's *documentation*, on machines with no WSL.
`NucleicBroker/Wslc/WslcFacade.cs` is therefore a careful transcription that has never been
compiled against the real assembly, and `-p:UseWslc=true` is the only thing standing between it
and the build. Every line in it might be right. We have no idea which.
**Status: it has served its original purpose (docs/WINDOWS_PORT.md §13.1).** The real API is
known, and it was obtained without a Windows machine at all — the package is public, so the
`.nupkg` was downloaded, its projection assembly extracted, and its metadata read with
`MetadataLoadContext`. What this tool is *for* has therefore changed: its assumption list now
describes the surface actually observed in 2.9.3, so running it says **what the next package
version moved**, not what we guessed wrong.
A typed spike cannot tell us: if `Session.CreateOrOpen` is really `Session.Open`, the spike fails
to **compile**, which teaches us one name and stops. So this tool is written entirely in
reflection. It cannot fail to build, and a single run prints the complete list of what is wrong.
**Why it is still reflection.** Same reason as before: a typed check fails to compile on the
first rename and reports one problem, where this reports all of them at once. That property is
worth keeping for a preview API whose next version can break anything.
Two facts it discovered that anything referencing this package needs:
- the assembly is **`wslcsdkcs.dll`**, not `Microsoft.WSL.Containers.dll` — loading it by package
name fails;
- the only published version is **2.9.3**, targeting `net8.0-windows10.0.19041.0`. The
`0.1.0-preview.1` pin this repo carried could never have restored.
```powershell
cd windows/spikes/WslcApiDump
@@ -33,7 +41,8 @@ It writes the full public object model to `wslc-api-dump.txt` (`--out` to reloca
It exits 0 even when assumptions fail — a mismatch is the product, not an error. Only a genuinely
broken run (the assembly won't load) exits non-zero.
**What to send back:** the console output, and `wslc-api-dump.txt` if anything is MISSING.
**What to send back:** the console output, and `wslc-api-dump.txt` if anything is MISSING —
which now means the package moved under us, not that we guessed wrong.
### Why `--session` earns its risk
+72 -61
View File
@@ -1,18 +1,19 @@
namespace WslcApiDump;
/// <summary>
/// Every member <c>NucleicBroker/Wslc/WslcFacade.cs</c> calls on the preview
/// <c>Microsoft.WSL.Containers</c> API, as plain strings.
/// The <c>Microsoft.WSL.Containers</c> surface `NucleicBroker/Wslc/WslcFacade.cs` depends on.
///
/// The facade was written from Microsoft's documentation without a machine to run it on
/// (docs/WINDOWS_PORT.md §1.5), so each line here is a *claim* — and this spike's job is to say,
/// for each one, whether the shipped assembly agrees. Strings rather than typed references is the
/// entire trick: a typed spike that names <c>Session.CreateOrOpen</c> fails to COMPILE if that
/// method has a different name, which teaches us nothing. Reflection turns every wrong guess into
/// a printed line instead of a build error, so one run produces the complete worklist.
/// **This list changed meaning on 2026-07-29.** It began as a list of *guesses* — the facade was
/// transcribed from documentation on machines with no WSL — and this tool existed to find out how
/// many were wrong. That question is answered (docs/WINDOWS_PORT.md §13.1: many of them, including
/// three that were missing capability rather than a wrong name). The entries below are now the
/// surface **actually observed in package 2.9.3**, so this tool's job has changed from discovery
/// to **drift detection**: run it after bumping the pin and it says what the new version moved.
///
/// Keep in lockstep with WslcFacade.cs. A member that stops being used should leave this list;
/// a newly-used member should join it, so the next run of this tool still covers the real surface.
/// Still strings rather than typed references, for the same reason as before — a typed check fails
/// to compile on the first rename and reports one problem; this reports all of them.
///
/// Keep in lockstep with WslcFacade.cs.
/// </summary>
internal static class FacadeAssumptions
{
@@ -23,83 +24,93 @@ internal static class FacadeAssumptions
internal static readonly Assumption[] All =
[
// ---- Service entry point: onboarding (§8 step 2) ----
new("WslcService", "GetServiceVersion", Kind.Method,
"hello capabilities — hostd degrades across preview→GA churn on this string"),
new("WslcService", "GetVersion", Kind.Method,
"hello capabilities — hostd degrades across preview→GA churn on this"),
new("WslcService", "GetMissingComponents", Kind.Method,
"components.missing RPC; drives the guided-install onboarding page"),
new("WslcService", "InstallComponentsAsync", Kind.Method,
"components.install RPC"),
new("ComponentFlags", "None", Kind.EnumValue,
"the 'nothing missing' sentinel the facade filters on"),
"components.missing RPC; returns IReadOnlyList<Component>, NOT a flags enum"),
new("WslcService", "InstallWithDependencies", Kind.Method, "components.install RPC"),
new("Component", "WslPackage", Kind.EnumValue,
"one of the three components onboarding can report missing"),
new("ServiceVersion", "Major", Kind.Property, "the version triple reported in hello"),
// ---- Session: one per channel, hosts every container (§3.2) ----
new("SessionSettings", ".ctor", Kind.Constructor,
"SessionSettings(name, dataDir) — the two-arg shape the facade assumes"),
new("SessionSettings", ".ctor", Kind.Constructor, "SessionSettings(name, storagePath)"),
new("SessionSettings", "CpuCount", Kind.Property, "session.ensure cpu"),
new("SessionSettings", "MemoryMB", Kind.Property,
"session.ensure memoryMB; §3.2 notes a resize needs a sandbox restart"),
new("Session", "CreateOrOpen", Kind.Method,
"THE create-or-attach primitive. If this is absent, broker reattach after a crash "
+ "(§2.3) has no mechanism and the whole supervision design changes"),
new("SessionSettings", "MemorySizeInMB", Kind.Property,
"session.ensure memoryMB — NOT MemoryMB; a resize needs a sandbox restart (§3.2)"),
new("SessionSettings", "Timeout", Kind.Property, "idle timeout for the whole session VM"),
new("Session", ".ctor", Kind.Constructor,
"there is NO CreateOrOpen — construction is the only entry point, and whether a second "
+ "construction with an existing name attaches or throws Error.SessionReserved is the "
+ "open question broker reattach (§2.3) hangs on"),
new("Session", "Start", Kind.Method, "brings the session VM up after construction"),
new("Session", "Terminate", Kind.Method, "session.terminate RPC"),
new("Session", "SessionTerminationHandler", Kind.Property,
"session.down notification → hostd's reconcile sweep"),
new("Session", "HostGatewayAddress", Kind.Property,
"THE control-plane address (§5). ensureRunning returns it to the Swift engine and "
+ "control-bridge.js dials it. If this member does not exist, §5's primary transport "
+ "needs another source (query the vNIC) or the hvsocket fallback gets promoted"),
new("Session", "Terminated", Kind.Event,
"session.down notification (SessionTerminationReason) → hostd's reconcile sweep"),
new("SessionTerminationReason", "Crashed", Kind.EnumValue,
"distinguishes a crash from an orderly shutdown in the session.down reason"),
// ---- Images ----
new("Session", "PullImageAsync", Kind.Method, "image.pull RPC (naros-agent from GHCR)"),
new("PullImageOptions", ".ctor", Kind.Constructor, "PullImageOptions(reference)"),
new("PullImageOptions", "Credentials", Kind.Property, "GHCR auth (registryAuth)"),
new("PullImageOptions", "Progress", Kind.Event,
"image.pullProgress → the existing controlDownloadProgress UI surface"),
new("RegistryCredentials", ".ctor", Kind.Constructor, "RegistryCredentials(user, password)"),
new("Session", "PullImage", Kind.Method, "image.pull RPC (naros-agent from GHCR), sync form"),
new("Session", "PullImageAsync", Kind.Method,
"the async form, which is where pull PROGRESS comes from (ImageProgress)"),
new("PullImageOptions", ".ctor", Kind.Constructor, "PullImageOptions(uri)"),
new("PullImageOptions", "RegistryAuth", Kind.Property,
"GHCR auth — a STRING, not a credentials object"),
new("ImageProgress", "CurrentBytes", Kind.Property,
"→ image.pullProgress → the existing controlDownloadProgress UI surface"),
new("ImageProgressStatus", "Downloading", Kind.EnumValue, "pull progress phase"),
new("Session", "GetImages", Kind.Method, "image.list / image.inspect"),
new("ImageInfo", "Name", Kind.Property, "image ref — NOT .Reference"),
new("ImageInfo", "Sha256", Kind.Property, "image digest — NOT .Digest"),
new("Session", "DeleteImage", Kind.Method, "image.delete"),
// ---- Containers ----
new("Session", "CreateContainer", Kind.Method, "container.create"),
new("Session", "GetContainers", Kind.Method, "container.list; reattach re-enumeration"),
new("ContainerSettings", ".ctor", Kind.Constructor, "ContainerSettings(image)"),
new("ContainerSettings", ".ctor", Kind.Constructor, "ContainerSettings(imageName)"),
new("ContainerSettings", "Name", Kind.Property, "channel-suffixed container naming"),
new("ContainerSettings", "Hostname", Kind.Property, "container.create hostname"),
new("ContainerSettings", "HostName", Kind.Property, "container.create hostname — capital N"),
new("ContainerSettings", "NetworkingMode", Kind.Property,
"NAT vs mirrored (§5 item 4) — determines how the guest reaches the host"),
"None | Bridged — NOT the NAT/mirrored pair §5 assumed"),
new("ContainerSettings", "Volumes", Kind.Property, "the NTFS worktree bind mount (D8)"),
new("ContainerSettings", "InitProcess", Kind.Property, "naros-init as PID 1 (docs/NAROS.md)"),
new("ContainerVolume", ".ctor", Kind.Constructor,
"ContainerVolume(hostPath, guestPath, readOnly) — the 3-arg shape"),
new("ContainerNetworkingMode", "", Kind.Type, "enum parsed from the RPC's networkingMode"),
"ContainerVolume(windowsPath, containerPath, readOnly)"),
new("ContainerNetworkingMode", "Bridged", Kind.EnumValue,
"the mode a container needs to reach the host at all (§5)"),
new("Container", "Start", Kind.Method, "container.start"),
new("Container", "Stop", Kind.Method, "container.stop(signal, grace)"),
new("Container", "Delete", Kind.Method, "container.delete"),
new("Container", "Stop", Kind.Method, "container.stop(Signal, TimeSpan)"),
new("Container", "Delete", Kind.Method, "container.delete(DeleteContainerOption)"),
new("Container", "State", Kind.Property, "container.state → running/stopped/absent"),
new("Container", "GetStatistics", Kind.Method,
"container.stats → ContainerResourceSample; the Swift engine folds deltas from it"),
new("Container", "RunProcess", Kind.Method, "proc.exec — the agent's own exec path"),
new("Container", "Inspect", Kind.Method,
"the ONLY per-container introspection there is — there is no GetStatistics(), so "
+ "container.stats has to exec cgroup reads instead (§13.1 finding 2)"),
new("Container", "CreateProcess", Kind.Method,
"proc.exec — NOT RunProcess, and it does not start the process"),
new("ContainerState", "Running", Kind.EnumValue, "the one state the facade tests by name"),
new("DeleteContainerFlags", "Force", Kind.EnumValue, "container.delete force"),
new("DeleteContainerOption", "Force", Kind.EnumValue, "container.delete force"),
new("Error", "ContainerNotFound", Kind.EnumValue,
"structured failure codes — a better source for the RPC's data.kind than string matching"),
// ---- Processes: the agent stdio path (§3.2 WslcProcessHandle) ----
new("ProcessSettings", "CmdLine", Kind.Property, "argv"),
new("ProcessSettings", "Environment", Kind.Property, "env"),
new("ProcessSettings", "CommandLine", Kind.Property, "argv — NOT CmdLine"),
new("ProcessSettings", "EnvironmentVariables", Kind.Property, "env — NOT Environment"),
new("ProcessSettings", "WorkingDirectory", Kind.Property, "cwd"),
new("ProcessSettings", "OutputMode", Kind.Property, "event-mode stdio, not polling"),
new("ProcessSettings", "Terminal", Kind.Property, "tty for the Terminal panel (§7)"),
new("ProcessSettings", "UserId", Kind.Property,
"runAsUID 501 (§3.2). If absent, exec falls back to a setpriv/su wrapper argv — "
+ "interceptors and nash don't care about the numeric uid, so this is recoverable"),
new("ProcessSettings", "GroupId", Kind.Property, "runAsGID"),
new("ProcessOutputMode", "Event", Kind.EnumValue,
"the mode that makes stdio push-based; polling would change the whole broker design"),
new("Signal", "", Kind.Type, "the enum Stop/Signal take; the RPC carries POSIX ints"),
new("Process", "OutputReceived", Kind.Event, "→ proc.stdout / proc.stderr notifications"),
"the mode that makes stdio push-based; polling would change the broker design"),
new("Process", "Start", Kind.Method,
"the second half of exec — handlers are attached between CreateProcess and this, which "
+ "is precisely why the API is split in two"),
new("Process", "OutputReceived", Kind.Event, "→ proc.stdout notifications"),
new("Process", "ErrorReceived", Kind.Event,
"→ proc.stderr — a SEPARATE event, not a stderr flag on one handler"),
new("Process", "Exited", Kind.Event, "→ proc.exit; must never overtake output (OutboundWriter)"),
new("Process", "WriteStdin", Kind.Method, "proc.stdin (NDJSON to the agent)"),
new("Process", "CloseStdin", Kind.Method, "proc.closeStdin"),
new("Process", "GetInputStream", Kind.Method,
"proc.stdin — a WinRT stream, not a WriteStdin call; closing it is proc.closeStdin"),
new("Process", "Signal", Kind.Method, "proc.signal — the agent's real POSIX Stop path"),
new("Process", "ResizeTerminal", Kind.Method, "proc.resize (tty mode)"),
new("Signal", "SIGKILL", Kind.EnumValue,
"signals are a NAMED enum; the RPC carries POSIX ints, so the broker maps them, and "
+ "anything outside None/HUP/INT/QUIT/KILL/TERM is unavailable"),
];
}
+4 -1
View File
@@ -20,7 +20,10 @@ namespace WslcApiDump;
/// </summary>
internal static class Program
{
private const string AssemblyName = "Microsoft.WSL.Containers";
/// The C#/WinRT PROJECTION assembly. The NuGet package is "Microsoft.WSL.Containers" but
/// the managed assembly it ships is `lib/net8.0-windows10.0.19041.0/wslcsdkcs.dll`, so
/// loading it by package name fails — which is exactly the first thing this tool found.
private const string AssemblyName = "wslcsdkcs";
private static int Main(string[] args)
{
+2 -2
View File
@@ -11,14 +11,14 @@
<OutputType>Exe</OutputType>
<AssemblyName>wslc-api-dump</AssemblyName>
<RootNamespace>WslcApiDump</RootNamespace>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<!-- Same pin as the broker (docs/WINDOWS_PORT.md §15). If restore fails because the
version moved, change it HERE first, find out what the new API looks like, and only
then bump the broker — that ordering is the whole point of this spike. -->
<PackageReference Include="Microsoft.WSL.Containers" Version="[0.1.0-preview.1]" />
<PackageReference Include="Microsoft.WSL.Containers" Version="[2.9.3]" />
</ItemGroup>
</Project>