Protocols

Apple driver protocol

Frames, descriptors, lifecycle, failure ownership and evidence at the C-to-Swift boundary.

Status: the common MWDP/1 frame protocol, frozen in 0.20.0, and Apple LAUNCH payload version 6 are implemented. Release 0.19.0 used the predecessor MWAC/1 magic; 0.20.0 migrated both ends together without a compatibility alias. Physical macOS 26 conformance remains a release gate.

Discovery requires the exact proof MAELYS-APPLE-PROBE/6 with control=mwdp/1, launch=6, resource-contract=abi10-warden-cgroup for the composite and inner-procfs=private. A driver reporting probe v5 or older is rejected during probe before an execution can be prepared; Warden never discovers an old driver and then fails late at LAUNCH.

Warden is C. Apple Container 1.3.0 is a Swift/XPC API. The optional maelys-warden-apple-container-driver keeps this language and framework boundary out of libmaelys-executor.

Processes and descriptors

This section describes descriptors in the host Swift driver process. It does not assign descriptors in the guest workload. See warden-driver-protocol.md for the three separate descriptor spaces and the common Apple/Krun envelope.

The control channel is a dedicated Unix stream socket. It is not stdin: the driver's standard descriptors are forwarded to the guest workload. SOCK_SEQPACKET is deliberately not required because it is not available for Unix socket pairs on every supported macOS version. Explicit framing preserves message boundaries while stream EOF gives the driver a reliable parent-death signal.

Both ends use FD_CLOEXEC, bounded payloads and deadlines. The child receives only fd 0, 1, 2 and the control socket mapped to fd 4. Secrets, OCI credentials and network credentials never appear in argv.

More concretely, Warden creates a private socket pair and installs one end as fd 4 when it execs the driver. The Swift code reads MWDP/1 frames from that fd. This is not a socket inherited by the guest and it is not made visible to the workload.

The native workload convention also calls its optional network control channel "fd 4", but that channel is created by a different spawn and installed in a different process. A descriptor number is only an index into the descriptor table of one process. The two fd 4 values therefore have no identity, lifetime or protocol in common. Neither Apple candidate currently advertises the workload fd-4 frontend.

Frame

Every integer is unsigned big-endian:

CODE
offset  size  field
0       4     ASCII "MWDP"
4       4     protocol version (1)
8       4     message type
12      4     flags (zero in v1)
16      8     request id
24      4     payload size, maximum 1 MiB
28      N     payload

The legal sequence is:

Request identifiers are nonzero and increase strictly for controller requests. Each response repeats the request identifier it answers; terminal EXIT repeats the LAUNCH identifier. ACK confirms command admission and therefore precedes application of a signal or forced stop, preventing an immediate workload exit from overtaking the response on the stream.

READY means the guest workload was started, not merely that the driver was spawned. Any error before READY causes Warden to kill/reap the driver and return spawn failure. There is no reselection or native fallback.

The current LAUNCH payload version is 6, and it is numbered independently of the Krun driver's own payload 11. It carries the selected candidate, immutable image reference and digest, platform, guest cwd, argv, normalized environment, canonical plan digest, the complete sealed ABI 10 execution budget and its digest, mounts, filesystem decisions, network destinations and the five attested guest-bundle paths/digests (manifest, Bubblewrap, maelys-init, maelys-guest-supervisor and amd64 probe). The plain backend rejects filesystem access rules but accepts view mappings, tmpfs and the mediated proxy bridge. The composite additionally accepts recursive READ/WRITE rules with ERROR/SKIP missing semantics. Both reject fd-4 external-channel networking; the composite also rejects DENY, PROTECT_CREATE and forms that require an inner seccomp layer.

For the composite, payload 6 drives two exact enforcement layers: service VM sizing and the Warden-owned nested workload cgroup. The private MWAR/2 report returns workload exit, resource observations and verified cleanup. The simple candidate reports resource-contract=none and remains ineligible. See the resource decision record.

The driver hashes every guest-bundle object again immediately before mounting it. It places the bundle on a Warden-reserved read-only mount after all user mounts and runs a bounded Bubblewrap preflight before READY. For an amd64 target, that preflight executes the pinned amd64 ELF through Rosetta from inside the final namespace.

Lifecycle and failure ownership

CODE
PREPARED -> DRIVER_STARTING -> GUEST_STARTING -> READY -> EXITED
                 |                  |              |
                 +------------------+--------------+-> CLEANING
                                                     -> CLEANED
                                                     -> CLEANUP_FAILED
  • normal signals are protocol events, not host signals;
  • Warden owns deadlines;
  • if the driver stops answering, Warden uses host SIGKILL as the final containment action;
  • if Warden dies, fd 4 reaches EOF and the driver calls stop then delete;
  • the driver uses Apple Container's useInit, vminitd supervision and container-domain stop. It does not invent a second guest PID 1;
  • inner Bubblewrap uses its normal PID namespace reaper. bwrap --as-pid-1 must not be used: in Bubblewrap that option disables installation of the reaper process;
  • a forced VM stop makes the workload exit result unknown. The receipt records containment completion separately from outcome knowledge.

Protocol stop does not yet prove domain deletion after an unresponsive driver has to be killed on the host. Consequently the backend does not advertise PROCESS_TREE_LIFECYCLE; a RunSpec requiring it is rejected rather than receiving a weaker lifecycle promise.

Host PR_SET_PDEATHSIG is Linux-only and cannot solve a macOS driver/VM failure. Inside the guest it can be defense in depth, but cgroup/container domain lifecycle remains authoritative.

Identity and evidence

At registration Warden canonicalizes, stats and SHA-256 hashes the driver. It rechecks device, inode, permissions and content before probe, prepare and spawn. The DeploymentContext observation must contain this exact evidence digest. The OCI target requires a resolved sha256: digest; mutable tags never decide a prepared artifact. MWDP transports the repository reference and digest as separate canonical fields; the driver joins them as reference@digest only when addressing Apple's image store, then rejects any different resolved digest before container creation.

Apple's APIs used by the driver are pinned through SwiftPM to apple/container 1.3.0 and apple/containerization 0.41.0. No CLI output is scraped.