v0.30.0 Phase 3 Native Failure and Lifecycle Review
Status: complete with no open finding (2026-09-03). Phase 3 qualifies the Phase 2 synchronous subprocess model against Darwin's actual process, executable-image, argument-size, permission, and wait-status behavior. It adds no public API and no test-only subprocess implementation.
Reviewed Boundary
The review traced every Command.status result from public source through owned launch storage, pipe creation, fork classification, exact-path exec, the close-on-exec report channel, wait retry, terminal-status decoding, public error selection, destruction, and the external helper observed by the host test. It also searched all standard source for a second fork, exec, wait, pipe, errno- mapping, or public process-error authority.
The executable qualification deliberately distinguishes three facts that a magic exit-code design would conflate: an executable that exits zero, an executable that exits nonzero, and an executable request rejected by the kernel. An ordinary helper that exits 127 remains ExitStatus.code() == 127; missing, denied, malformed-image, and oversized-argument exec requests instead cross the four-byte report channel and return their stable public errors.
Native Behavior Matrix
One external program uses only the public std/process contract and passes each case through a complete compile, native-image, launch, and observation session:
- exact absolute paths exiting 0, 23, and 127;
- termination by
SIGTERM, observed exclusively as signal 15; - a missing executable mapped to
std.process.not_found; - a non-executable file mapped to
std.process.permission_denied; - an executable-mode non-image mapped from
ENOEXECtostd.process.invalid_input; - a relative
./relative-helperpath resolved by the inherited working directory; - ordered arguments containing both an empty value and embedded whitespace;
- embedded-NUL rejection before launch without partially changing the command;
- a two-megabyte argument mapped from Darwin
E2BIGtostd.process.invalid_input; - 96 consecutive rejected exec requests followed by another successful child.
The host creates executable fixtures but does not interpret Nocter process results. Scripts use kernel shebang handling only as compact executable fixtures; Command still performs one direct execve of the exact caller-provided path and never invokes a shell API.
Channel and Wait Lifecycle
Native standard tests drive the one launch-report decoder through clean close, a complete errno, an EOF after only two payload bytes, and a read from an invalid descriptor. The four outcomes are respectively executed, rejected, malformed, and channel_failed; every descriptor opened by the fixture is closed on both success and assertion failure paths. Repeated real exec rejection followed by success additionally exercises parent and child descriptor cleanup through the public operation rather than a mock.
Interrupted wait4 is not reliably inducible without adding signal-handler or thread machinery outside the subprocess contract. Qualification therefore injects its raw { errno: EINTR } fact at the existing target-transition boundary. classify_wait_attempt produces only WaitAttempt.interrupted, and the exhaustive loop in wait_for_child is the sole consumer: that variant performs another syscall and cannot enter the narrower WaitOutcome. There is no public, policy, or compiler path capable of interpreting interruption as terminal completion.
The parent reads and closes the report channel before waiting for the exact pid, then selects a public result only after wait returns a terminal outcome. Consequently every normal, signaled, and exec-rejected child created by these tests is observed and reaped. Pipe setup and fork failures create no child; a genuine terminal-observation failure remains the explicit std.process.wait_failed boundary rather than claiming a status the runtime did not observe.
Architecture Review
No duplicate launch authority was found. subprocess_darwin.nct alone owns raw syscalls, descriptor protocol, EINTR handling, and wait-bit decoding. command_darwin.nct alone owns public error selection and synchronous parent orchestration. command.nct alone owns validated storage and argv construction. The compiler supplies only closed process-context and pair-syscall primitive facts; it cannot construct a Command, decode wait status, or select a public process error.
The qualification source does not reproduce those decisions: it supplies paths and arguments, then observes only documented ExitStatus and error operations. Host code supplies filesystem preconditions and checks the outer program's zero exit. No caller must close a process descriptor, retain borrowed command text, distinguish exit 127, or remember to reap a child.
Qualification
The final gate passes focused public subprocess tests, all native-session tests, locked complete- workspace tests, warnings-denied Clippy for every target, no-default-features checking, Rust formatting, documentation generation, generated-output reproducibility, and diff whitespace validation.
No open Phase 3 remediation remains. Phase 4 can exercise the same contract through a runnable example, installed home, formatter, and editor tooling without changing this lifecycle model.