Resolved all 11 open questions based on project guidance: Transport: - OQ-01/OQ-07: ACME/Let's Encrypt with domain + IP paths (ADR-008) - OQ-02: Default to n0 relay, --iroh-relay override (ADR-009) - OQ-05: Transport chaining supported natively (ADR-010) Client: - OQ-06: Programmatic-first API, no ~/.ssh/config (ADR-011) Server: - OQ-04: Ed25519 + OpenSSH cert-authority, no password auth (ADR-012) - OQ-08: fail2ban-friendly logging + built-in rate limiting (ADR-013) TUN: - OQ-03/OQ-09: Deferred entirely, recommend tun2proxy (ADR-014) - tun-shim.md marked deprecated NAPI: - OQ-10: Expose both connect() and serve() (ADR-016) - OQ-11: Use napi-rs for FFI bridge (ADR-015) Additional ADRs created during review: - ADR-006: No logging of tunnel destinations (was phantom reference) - ADR-017: Stealth mode protocol multiplexing - ADR-018: Control channel for pubsub over SSH Fixed: ADR-002 status → Superseded, ADR-007 title typo, WRAUTH_SERVER typo, ADR-005 stale wraith-tun refs, undefined ACL feature removed from server.md, --proxy semantic difference documented.
1.7 KiB
1.7 KiB
ADR-007: NAPI Exposes Single Duplex Stream
Status
Accepted
Context
The NAPI wrapper for wraith could expose different granularity levels:
- Full SSH API: Expose channel multiplexing,
open_direct_tcpip,tcpip_forward, session management. The TypeScript layer would manage channels. - Single duplex stream: The NAPI wrapper establishes one SSH channel and returns it as a Node.js
Duplexstream. TypeScript multiplexing (if needed) happens at the pubsub layer.
Decision
Option 2: NAPI exposes a single duplex stream.
The NAPI wrapper's job is to get a reliable, authenticated byte stream from A to B. It handles transport (TCP/TLS/iroh), SSH authentication, and channel setup, then hands the caller a single Duplex stream that just works.
If the TypeScript consumer needs multiplexing (e.g., multiple concurrent tool calls over operations), pubsub handles that at the EventEnvelope level. Multiple call.requested / call.responded events flow over the same stream, distinguished by their id fields. This is how the existing WebSocket adapter works.
Consequences
- Positive: Minimal NAPI surface — one function, one return type. Small binary, small FFI boundary.
- Positive: The TypeScript side doesn't need to understand SSH at all. It gets a stream and sends/receives
EventEnvelopeJSON. - Positive: No need to expose russh types in NAPI. The SSH complexity stays in Rust.
- Negative: If a consumer wants multiple isolated channels (e.g., one for events, one for file transfer), they'd need multiple
connect()calls (multiple SSH sessions). This is acceptable for the expected use case (pubsub events over a single stream).