From e162d168de1de17b4e20fb0749b02dfabbe53814 Mon Sep 17 00:00:00 2001 From: AviBackToBlack <54722547+AviBackToBlack@users.noreply.github.com> Date: Thu, 1 Oct 2026 00:01:46 +0100 Subject: [PATCH 1/2] Add proof-bound WSL container resize --- docs/roadmap-implementation-requirements.md | 9 +-- docs/wsl-process-contract.md | 7 +++ docs/wsl.md | 17 +++--- internal/wsldocker/resize.go | 38 ++++++++++++ internal/wsldocker/resize_linux.go | 17 ++++++ internal/wsldocker/resize_other.go | 14 +++++ internal/wsldocker/resize_test.go | 64 +++++++++++++++++++++ internal/wsldocker/wsldocker.go | 5 +- 8 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 internal/wsldocker/resize.go create mode 100644 internal/wsldocker/resize_linux.go create mode 100644 internal/wsldocker/resize_other.go create mode 100644 internal/wsldocker/resize_test.go diff --git a/docs/roadmap-implementation-requirements.md b/docs/roadmap-implementation-requirements.md index abf9b5d..20f0d9a 100644 --- a/docs/roadmap-implementation-requirements.md +++ b/docs/roadmap-implementation-requirements.md @@ -607,10 +607,11 @@ registry-derived management/tool-shim reconciliation without enabling tool execution. Canonical project storage classification with its proof-consuming argument mapper, fail-closed Docker Desktop WSL integration proof and a proof-bound bounded Engine API control-request primitive, constrained attach -transport and exact context-bound container-wait operation are implemented but -not yet wired into an enabled frontend. Multiplexed-output decoding, -terminal/resize, signal and end-to-end exit-code propagation remain. Runtime -wiring, argument/process behavior and real WSL qualification remain. +transport, strict multiplexed-output decoder, exact context-bound +container-wait operation and proof-bound container-TTY resize operation are +implemented but not yet wired into an enabled frontend. Terminal event +collection, signal and end-to-end exit-code propagation remain. Runtime wiring, +argument/process behavior and real WSL qualification remain. Implementation must define native config/shim location, Docker endpoint, project identity, named-volume behavior, file permissions, case sensitivity, diff --git a/docs/wsl-process-contract.md b/docs/wsl-process-contract.md index b8d36f6..ea3855c 100644 --- a/docs/wsl-process-contract.md +++ b/docs/wsl-process-contract.md @@ -52,6 +52,13 @@ unknown stream identifiers, truncated frames, malformed reserved bytes, and unsafe daemon-error payloads fail closed. This primitive does not enable the frontend or change the existing Docker CLI path. +The same proof-bound path has an explicit container-TTY resize operation. It +accepts only an exact full container ID and positive unsigned 16-bit height and +width values, sends them to Docker's fixed resize endpoint, and accepts only an +HTTP 200 response. It does not inspect the caller's terminal or subscribe to +resize events; later frontend wiring must supply dimensions from a proven TTY +and invoke the operation for each accepted resize event. + `docker run` always receives `-i`. It additionally receives `-t` only when both stdin and stdout report character-device mode; either redirection, either stat failure, or a non-character stream keeps the invocation non-TTY. Stderr does diff --git a/docs/wsl.md b/docs/wsl.md index 015fee1..6d314f9 100644 --- a/docs/wsl.md +++ b/docs/wsl.md @@ -237,10 +237,12 @@ accepts only an exact full container ID and fixes the request to `condition=not-running`. It repeats the socket/peer proof, uses the caller's context as the long-poll lifetime, bounds the response to 64 KiB, rejects an unsafe Engine error, and accepts only process exit codes from 0 through 255. -These primitives do not decode multiplexed output or implement container -creation/start, terminal behavior, resize, signals, or end-to-end exit-code -propagation. Nothing is wired into tool execution yet; real WSL2 + Docker -Desktop qualification remains mandatory before support. +The native package also has a strict decoder for non-TTY multiplexed output and +a proof-bound resize operation for one exact full container ID with positive +unsigned 16-bit terminal dimensions. These primitives do not implement +container creation/start, terminal event collection, signals, or end-to-end +exit-code propagation. Nothing is wired into tool execution yet; real WSL2 + +Docker Desktop qualification remains mandatory before support. ## Native WSL volume identity and control lifecycle @@ -283,10 +285,9 @@ following: mapper into native tool execution, then complete stdin/TTY and signal semantics; 3. wire the implemented bounded Docker Desktop control-operation and attach - primitives, raw-stream decoder and wait primitive into container lifecycle, - then implement terminal/resize, signal and exit-code propagation without - accepting ambient - endpoint overrides; + primitives, raw-stream decoder, wait and resize operations into container + lifecycle, then implement terminal event collection, signal and exit-code + propagation without accepting ambient endpoint overrides; 4. Windows-filesystem and WSL-filesystem project tests plus mixed-invocation rejection; and 5. real WSL2 + Docker Desktop end-to-end qualification before any support claim. diff --git a/internal/wsldocker/resize.go b/internal/wsldocker/resize.go new file mode 100644 index 0000000..fcfa9f0 --- /dev/null +++ b/internal/wsldocker/resize.go @@ -0,0 +1,38 @@ +package wsldocker + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" +) + +func resizeContainer(ctx context.Context, containerID string, height, width uint16, deps operationDependencies) error { + if ctx == nil { + return errors.New("Docker Desktop WSL container resize requires a context") + } + if err := validateContainerID(containerID); err != nil { + return fmt.Errorf("Docker Desktop WSL container resize: %w", err) + } + if height == 0 { + return errors.New("Docker Desktop WSL container resize requires a positive height") + } + if width == 0 { + return errors.New("Docker Desktop WSL container resize requires a positive width") + } + if deps.check == nil || deps.statSocket == nil || deps.perform == nil { + return errors.New("Docker Desktop WSL container resize dependencies are incomplete") + } + _, err := execute(ctx, Request{ + Method: http.MethodPost, + Path: "/containers/" + containerID + "/resize", + Query: url.Values{ + "h": {strconv.FormatUint(uint64(height), 10)}, + "w": {strconv.FormatUint(uint64(width), 10)}, + }, + SuccessStatuses: []int{http.StatusOK}, + }, deps) + return err +} diff --git a/internal/wsldocker/resize_linux.go b/internal/wsldocker/resize_linux.go new file mode 100644 index 0000000..4585f87 --- /dev/null +++ b/internal/wsldocker/resize_linux.go @@ -0,0 +1,17 @@ +//go:build linux + +package wsldocker + +import "context" + +// ResizeContainer resizes the TTY for one exact container. Dimensions must be +// positive and fit the terminal's unsigned 16-bit row/column representation. +func ResizeContainer(ctx context.Context, containerID string, height, width uint16) error { + return resizeContainer(ctx, containerID, height, width, operationDependencies{ + check: Check, + statSocket: statDockerSocket, + perform: func(ctx context.Context, socketPath string, request Request) (operationResult, error) { + return performDockerRequest(ctx, socketPath, request, operationTimeout, maxOperationOutput, 0) + }, + }) +} diff --git a/internal/wsldocker/resize_other.go b/internal/wsldocker/resize_other.go new file mode 100644 index 0000000..9938b45 --- /dev/null +++ b/internal/wsldocker/resize_other.go @@ -0,0 +1,14 @@ +//go:build !linux + +package wsldocker + +import ( + "context" + "errors" +) + +// ResizeContainer is unavailable outside Linux because the fixed Unix socket +// and peer credentials are part of the Docker Desktop WSL trust boundary. +func ResizeContainer(context.Context, string, uint16, uint16) error { + return errors.New("Docker Desktop WSL container resize requires Linux") +} diff --git a/internal/wsldocker/resize_test.go b/internal/wsldocker/resize_test.go new file mode 100644 index 0000000..83d407e --- /dev/null +++ b/internal/wsldocker/resize_test.go @@ -0,0 +1,64 @@ +package wsldocker + +import ( + "context" + "net/http" + "strings" + "testing" +) + +func TestResizeContainerBindsExactDimensionsToProvenSocket(t *testing.T) { + socket := validSocketInfo() + statCalls := 0 + deps := validOperationDependencies(socket) + deps.statSocket = func(path string) (socketInfo, error) { + if path != DockerSocketPath { + t.Fatalf("stat path = %q", path) + } + statCalls++ + return socket, nil + } + deps.perform = func(_ context.Context, path string, request Request) (operationResult, error) { + if path != DockerSocketPath || request.Method != http.MethodPost || request.Path != "/containers/"+testContainerID+"/resize" { + t.Fatalf("perform(%q, %+v)", path, request) + } + if request.Query.Encode() != "h=24&w=80" || len(request.Body) != 0 || len(request.SuccessStatuses) != 1 || request.SuccessStatuses[0] != http.StatusOK { + t.Fatalf("resize request = %+v", request) + } + return operationResult{StatusCode: http.StatusOK, PeerUID: 0}, nil + } + if err := resizeContainer(context.Background(), testContainerID, 24, 80, deps); err != nil { + t.Fatal(err) + } + if statCalls != 2 { + t.Fatalf("stat calls = %d", statCalls) + } +} + +func TestResizeContainerRejectsInvalidInputsBeforeProof(t *testing.T) { + tests := map[string]struct { + ctx context.Context + containerID string + height uint16 + width uint16 + }{ + "nil context": {containerID: testContainerID, height: 24, width: 80}, + "short ID": {ctx: context.Background(), containerID: "abc", height: 24, width: 80}, + "uppercase ID": {ctx: context.Background(), containerID: strings.ToUpper(testContainerID), height: 24, width: 80}, + "zero height": {ctx: context.Background(), containerID: testContainerID, width: 80}, + "zero width": {ctx: context.Background(), containerID: testContainerID, height: 24}, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + deps := validOperationDependencies(validSocketInfo()) + deps.check = func(context.Context) (Result, error) { panic("proof reached for invalid resize") } + if err := resizeContainer(test.ctx, test.containerID, test.height, test.width, deps); err == nil { + t.Fatal("resizeContainer() succeeded") + } + }) + } + + if err := resizeContainer(context.Background(), testContainerID, 24, 80, operationDependencies{}); err == nil || !strings.Contains(err.Error(), "dependencies are incomplete") { + t.Fatalf("incomplete-dependencies error = %v", err) + } +} diff --git a/internal/wsldocker/wsldocker.go b/internal/wsldocker/wsldocker.go index 0dd5a97..c9ffbc3 100644 --- a/internal/wsldocker/wsldocker.go +++ b/internal/wsldocker/wsldocker.go @@ -1,8 +1,9 @@ // Package wsldocker proves that a native WSL2 process is connected to Docker // Desktop's supported WSL integration rather than an in-distribution or remote // Docker Engine, and provides proof-bound bounded control requests plus -// separately constrained container-attach and wait transports. It does not -// enable the WSL frontend by itself. +// separately constrained container-attach and wait transports plus an exact +// proof-bound TTY-resize operation. It does not enable the WSL frontend by +// itself. package wsldocker import ( From 6138bab1f7b25fdabf9c2f1978adfc766f79f6d6 Mon Sep 17 00:00:00 2001 From: AviBackToBlack <54722547+AviBackToBlack@users.noreply.github.com> Date: Thu, 1 Oct 2026 11:28:25 +0100 Subject: [PATCH 2/2] Address WSL resize review nits --- docs/roadmap-decisions.md | 7 ++++--- internal/wsldocker/resize_test.go | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/roadmap-decisions.md b/docs/roadmap-decisions.md index 91adb50..ad6c2e2 100644 --- a/docs/roadmap-decisions.md +++ b/docs/roadmap-decisions.md @@ -344,9 +344,10 @@ is not completion. preparation plus the fixed-path native install/config lifecycle are implemented; ordinary runtime integration remains gated; - Docker Desktop WSL integration proof, proof-bound bounded control requests, - and the separately constrained proof-bound attach transport are implemented - but not yet wired into an enabled frontend; multiplexed output, terminal, - resize, signal and exit-code semantics remain; + the separately constrained attach transport, strict raw-stream decoder, + exact container wait and TTY-resize operations are implemented but not yet + wired into an enabled frontend; terminal event collection, signal and + end-to-end exit-code propagation remain; - namespace-prefixed/labeled WSL volume identity plus proof-bound exact inspect/create/remove and namespace discovery are implemented; tool-time creation and state/GC/backup/restore command integration remain; diff --git a/internal/wsldocker/resize_test.go b/internal/wsldocker/resize_test.go index 83d407e..46b1f51 100644 --- a/internal/wsldocker/resize_test.go +++ b/internal/wsldocker/resize_test.go @@ -45,6 +45,7 @@ func TestResizeContainerRejectsInvalidInputsBeforeProof(t *testing.T) { "nil context": {containerID: testContainerID, height: 24, width: 80}, "short ID": {ctx: context.Background(), containerID: "abc", height: 24, width: 80}, "uppercase ID": {ctx: context.Background(), containerID: strings.ToUpper(testContainerID), height: 24, width: 80}, + "non-hex ID": {ctx: context.Background(), containerID: strings.Repeat("g", 64), height: 24, width: 80}, "zero height": {ctx: context.Background(), containerID: testContainerID, width: 80}, "zero width": {ctx: context.Background(), containerID: testContainerID, height: 24}, }