/// The ONLY WS I/O boundary (frozen contract, plan §3.1/§3.2).
/// `URLSessionTermTransport` (SessionCore, T-iOS-9) and `FakeTransport`
/// (TestSupport, T-iOS-4) both implement this; `SessionEngine` cannot tell
/// them apart.
public protocol TermTransport: Sendable {
    /// Open a WS connection to `endpoint.wsURL`, stamping
    /// `Origin: endpoint.originHeader` on the upgrade (plan §5.1).
    func connect(to endpoint: HostEndpoint) async throws -> TransportConnection
}

/// One live WS connection, as immutable capability handles (frozen contract).
public struct TransportConnection: Sendable {
    /// Server JSON text frames, in arrival order. Stream finish = clean close;
    /// stream throw = transport error (the two are distinguishable, T-iOS-9).
    public let frames: AsyncThrowingStream<String, any Error>
    /// Send one client JSON text frame (produced by `MessageCodec.encode`).
    public let send: @Sendable (String) async throws -> Void
    /// Close the connection (client detach — the server-side PTY keeps running).
    public let close: @Sendable () async -> Void

    public init(
        frames: AsyncThrowingStream<String, any Error>,
        send: @escaping @Sendable (String) async throws -> Void,
        close: @escaping @Sendable () async -> Void
    ) {
        self.frames = frames
        self.send = send
        self.close = close
    }
}
