Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,9 +463,11 @@ separate attach transport admits only a live-stream POST for an exact full
container ID, repeats the complete socket/peer proof, bounds the upgrade and
error response, and returns a context-bound duplex stream with explicit TTY
framing metadata and independent stdin half-close. Parent cancellation closes
the upgraded connection and unblocks I/O. Container lifecycle,
multiplexed-output decoding, terminal behavior, and signal forwarding remain
outside that primitive.
the upgraded connection and unblocks I/O. Sibling proof-bound primitives decode
strict non-TTY multiplexed output and perform exact container inspection, wait,
TTY resize and start operations. They are not yet wired into an enabled
container lifecycle; creation, terminal event collection, signal handling and
end-to-end exit propagation remain.

`internal/wslvolume` defines the WSL Docker-volume identity and bounded control
lifecycle. A volume name starts with `cb-<wsl-namespace>-`;
Expand Down
7 changes: 4 additions & 3 deletions docs/roadmap-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,9 +347,10 @@ is not completion.
implemented; ordinary runtime integration remains gated;
- Docker Desktop WSL integration proof, proof-bound bounded control requests,
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;
exact container inspection, wait, TTY-resize and start operations are
implemented but not yet wired into an enabled frontend; container
creation, 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;
Expand Down
9 changes: 5 additions & 4 deletions docs/roadmap-implementation-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The minimum delivery gate for a code change is:
| Enterprise policy | **Foundation and signed registry shipped / image trust remains** | PRs #75 and #84 shipped the machine-owned constraint layer and authenticated registry; image trust remains |
| Image trust | **Online/offline production and runtime authorization implemented / private-registry work remains** | Add an explicit private-registry credential bridge |
| Plugin/provider architecture | **Intentionally deferred** | Reopen only after at least two real integrations cannot fit the declarative model |
| WSL2 | **Installer foundation implemented / runtime qualification remaining** | PRs #77 and #83 shipped the fail-closed host boundary and fixed native layout/state identity; explicit read-only/apply filesystem preparation and the fixed-path native install/config/shim lifecycle are available alongside namespace-prefixed/labeled volume identity with proof-bound exact inspect/create/remove/discovery, Docker Desktop integration proof, bounded control requests, constrained attach and exact context-bound wait transports, while command/frontend wiring, stream/terminal/signal semantics and real WSL qualification remain |
| WSL2 | **Installer foundation implemented / runtime qualification remaining** | PRs #77 and #83 shipped the fail-closed host boundary and fixed native layout/state identity; explicit read-only/apply filesystem preparation and the fixed-path native install/config/shim lifecycle are available alongside namespace-prefixed/labeled volume identity with proof-bound exact inspect/create/remove/discovery, Docker Desktop integration proof, bounded control requests, constrained attach, strict raw-stream decoding, exact container inspection, exact context-bound wait, proof-bound TTY-resize and exact container-start operations, while command/frontend wiring, container creation, terminal event collection, signal/exit semantics and real WSL qualification remain |
| Per-project overlays | **Completed in PR #80** | Add-only digest-bound trust model shipped on the merged enterprise-policy foundation |
| Release SBOM | **Conditionally deferred** | Trigger on shipped third-party/runtime dependencies or concrete compliance/consumer demand |
| Snyk | **Conditionally deferred** | Trigger only for a real coverage gap plus owner/account/token and triage/outage policy |
Expand Down Expand Up @@ -610,9 +610,10 @@ 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, 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,
container inspection, container-wait, container-TTY resize and container-start
operations are implemented but not yet wired into an enabled frontend.
Container creation, 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,
Expand Down
19 changes: 14 additions & 5 deletions docs/wsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,18 @@ 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.
A separate inspect operation accepts only an exact full container ID, bounds
the response to 1 MiB, requires the returned ID to match exactly and rejects
missing lifecycle/terminal fields. It exposes only the immutable ID, running,
TTY, stdin and defensively copied label state needed by later lifecycle checks.
A separate start operation accepts only an exact full container ID, repeats
the socket/peer proof, fixes the request to `POST /containers/{id}/start`, and
accepts only HTTP 204. Docker's HTTP 304 "already started" response fails
closed instead of being treated as an idempotent success.
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
container creation, 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.

Expand Down Expand Up @@ -284,10 +292,11 @@ following:
2. wire the implemented project-root selector, project boundary and argument
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, wait and resize operations into container
lifecycle, then implement terminal event collection, signal and exit-code
propagation without accepting ambient endpoint overrides;
3. wire the implemented bounded Docker Desktop control-operation, attach,
inspect, wait, resize and exact container-start primitives plus the
raw-stream decoder into container lifecycle, then implement container
creation, 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.
Expand Down
26 changes: 26 additions & 0 deletions internal/wsldocker/start.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package wsldocker

import (
"context"
"errors"
"fmt"
"net/http"
)

func startContainer(ctx context.Context, containerID string, deps operationDependencies) error {
if ctx == nil {
return errors.New("Docker Desktop WSL container start requires a context")
}
if err := validateContainerID(containerID); err != nil {
return fmt.Errorf("Docker Desktop WSL container start: %w", err)
}
if deps.check == nil || deps.statSocket == nil || deps.perform == nil {
return errors.New("Docker Desktop WSL container start dependencies are incomplete")
}
_, err := execute(ctx, Request{
Method: http.MethodPost,
Path: "/containers/" + containerID + "/start",
SuccessStatuses: []int{http.StatusNoContent},
}, deps)
return err
}
17 changes: 17 additions & 0 deletions internal/wsldocker/start_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build linux

package wsldocker

import "context"

// StartContainer starts one exact stopped container through the proof-bound
// Docker Desktop WSL socket. Already-running containers fail closed.
func StartContainer(ctx context.Context, containerID string) error {
return startContainer(ctx, containerID, operationDependencies{
check: Check,
statSocket: statDockerSocket,
perform: func(ctx context.Context, socketPath string, request Request) (operationResult, error) {
return performDockerRequest(ctx, socketPath, request, operationTimeout, maxOperationOutput, 0)
},
})
}
14 changes: 14 additions & 0 deletions internal/wsldocker/start_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build !linux

package wsldocker

import (
"context"
"errors"
)

// StartContainer is unavailable outside Linux because the fixed Unix socket
// and peer credentials are part of the Docker Desktop WSL trust boundary.
func StartContainer(context.Context, string) error {
return errors.New("Docker Desktop WSL container start requires Linux")
}
74 changes: 74 additions & 0 deletions internal/wsldocker/start_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package wsldocker

import (
"context"
"errors"
"net/http"
"strings"
"testing"
)

func TestStartContainerBindsExactRequestToProvenSocket(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+"/start" {
t.Fatalf("perform(%q, %+v)", path, request)
}
if len(request.Query) != 0 || len(request.Body) != 0 || len(request.SuccessStatuses) != 1 || request.SuccessStatuses[0] != http.StatusNoContent {
t.Fatalf("start request = %+v", request)
}
return operationResult{StatusCode: http.StatusNoContent, PeerUID: 0}, nil
}
if err := startContainer(context.Background(), testContainerID, deps); err != nil {
t.Fatal(err)
}
if statCalls != 2 {
t.Fatalf("stat calls = %d, want 2", statCalls)
}
}

func TestStartContainerRejectsInvalidInputsBeforeProof(t *testing.T) {
for name, containerID := range map[string]string{
"short": "abc",
"uppercase": strings.ToUpper(testContainerID),
"non hex": strings.Repeat("g", 64),
} {
t.Run(name, func(t *testing.T) {
deps := validOperationDependencies(validSocketInfo())
deps.check = func(context.Context) (Result, error) { panic("proof reached for invalid container ID") }
if err := startContainer(context.Background(), containerID, deps); err == nil {
t.Fatal("startContainer() succeeded")
}
})
}

deps := validOperationDependencies(validSocketInfo())
deps.check = func(context.Context) (Result, error) { panic("proof reached with nil context") }
if err := startContainer(nil, testContainerID, deps); err == nil {
t.Fatal("startContainer() accepted nil context")
}
if err := startContainer(context.Background(), testContainerID, operationDependencies{}); err == nil || !strings.Contains(err.Error(), "dependencies are incomplete") {
t.Fatalf("incomplete-dependencies error = %v", err)
}
}

func TestStartContainerRejectsAlreadyRunning(t *testing.T) {
deps := validOperationDependencies(validSocketInfo())
deps.perform = func(context.Context, string, Request) (operationResult, error) {
return operationResult{StatusCode: http.StatusNotModified, Raw: []byte(`{"message":"container is already running"}`), PeerUID: 0}, nil
}
err := startContainer(context.Background(), testContainerID, deps)
var apiErr *APIError
if !errors.As(err, &apiErr) || apiErr.StatusCode != http.StatusNotModified || apiErr.Message != "container is already running" {
t.Fatalf("startContainer() error = %#v", err)
}
}
5 changes: 2 additions & 3 deletions internal/wsldocker/wsldocker.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// 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 plus an exact
// proof-bound TTY-resize operation. It does not enable the WSL frontend by
// itself.
// separately constrained container-attach, inspect, wait, TTY-resize and start
// transports. It does not enable the WSL frontend by itself.
package wsldocker

import (
Expand Down
Loading