Merge nucleic/olive-jade-civet-rznt into dev
This commit is contained in:
+18
-9
@@ -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
|
||||
|
||||
|
||||
@@ -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"),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user