[Review] SF-0037: Subprocess 1.0

Copying my suggestion from the original thread.

Consider the following "indirect" design for the protocols:

enum InputMethod {
    case pipe((StandardInputWriter) async throws -> Void)
    case fileDescriptor(FileDescriptor, closeAfterSpawning: Bool)
    case devNull
}

public protocol InputProtocol: Sendable {
    var inputMethod: InputMethod { get }
}
enum OutputMethod<T: Sendable> {
    case collect(maxSize: Int, (RawSpan) throws -> T)
    case fileDescriptor(FileDescriptor, closeAfterSpawning: Bool)
    case stream
    case discard
}

public protocol OutputProtocol: Sendable {
    associatedtype OutputType: Sendable
    var outputMethod: OutputMethod<OutputType> { get }
}
The conformances
struct NoInput: InputProtocol {
    var inputMethod: InputMethod { .devNull }
}
struct FileDescriptorInput: InputProtocol {
    let fd: FileDescriptor
    let closeAfterSpawning: Bool
    var inputMethod: InputMethod {
        .fileDescriptor(fd, closeAfterSpawning: closeAfterSpawning)
    }
}
struct StringInput: InputProtocol {
    let string: String
    var inputMethod: InputMethod {
        .pipe { writer in
            try await writer.write(string.utf8)
            try await writer.finish()
        }
    }
}
struct ArrayInput: InputProtocol {
    let array: [UInt8]
    var inputMethod: InputMethod {
        .pipe { writer in
            try await writer.write(array)
            try await writer.finish()
        }
    }
}
struct DiscardedOutput: OutputProtocol {
    typealias OutputType = Void
    var outputMethod: OutputMethod<Void> { .discard }
}
struct FileDescriptorOutput: OutputProtocol {
    let fd: FileDescriptor
    let closeAfterSpawning: Bool
    typealias OutputType = Void
    var outputMethod: OutputMethod<Void> {
        .fileDescriptor(fd, closeAfterSpawning: closeAfterSpawning)
    }
}
struct StringOutput: OutputProtocol {
    let limit: Int
    let encoding: any Encoding.Type
    typealias OutputType = String
    var outputMethod: OutputMethod<String> {
        .collect(maxSize: limit) { String(decoding: $0, as: encoding) }
    }
}
struct BytesOutput: OutputProtocol {
    let limit: Int
    typealias OutputType = [UInt8]
    var outputMethod: OutputMethod<[UInt8]> {
        .collect(maxSize: limit) { Array($0) }
    }
}
struct SequenceOutput: OutputProtocol {
    typealias OutputType = Void
    var outputMethod: OutputMethod<Void> { .stream }
}
Changes to run method
switch input.inputMethod {
case .devNull:
    // redirect to /dev/null
case .fileDescriptor(let fd, let close):
    // use fd directly
case .pipe(let writeFn):
    // create pipe, call writeFn(writer)
}