Decompose architecture into 35 atomic tasks across 10 generations for implementation

This commit is contained in:
2026-06-02 09:02:55 +00:00
parent b5c59ef3bc
commit 14dbd81195
35 changed files with 1636 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
---
id: transport/acme-cert-provisioning
name: Implement ACME Lets Encrypt certificate provisioning (feature-gated acme)
status: pending
depends_on:
- transport/tls-transport
scope: moderate
risk: high
impact: component
level: implementation
---
## Description
Implement automatic TLS certificate provisioning via ACME (Let's Encrypt). Two modes per ADR-008:
1. **Domain-based ACME** (`--acme-domain`): Standard flow with HTTP-01 or TLS-ALPN-01 challenges. Domain-bound, auto-renewing.
2. **IP-based ACME**: Short-lived certs via TLS-ALPN-01 on port 443. No domain needed.
Uses `rustls-acme` (pure Rust) to avoid external certbot dependency. Feature-gated behind `acme` (implies `tls`).
This integrates with `TlsAcceptor` by providing ACME-resolved certificates instead of manual cert/key files.
## Acceptance Criteria
- [ ] `crates/wraith-core/src/transport/acme.rs` (behind `#[cfg(feature = "acme")]`)
- [ ] Feature `acme` implies `tls` in Cargo.toml
- [ ] `AcmeCertProvider` struct accepts: domain (domain-based) or IP mode flag
- [ ] Domain-based mode: uses `rustls-acme` with HTTP-01/TLS-ALPN-01 challenge responder
- [ ] IP-based mode: uses `rustls-acme` with TLS-ALPN-01 on port 443
- [ ] `AcmeCertProvider` produces a `rustls::ServerConfig` that `TlsAcceptor` can use
- [ ] Certificate auto-renewal handled by `rustls-acme` background task
- [ ] `TlsAcceptor` updated to accept either manual certs OR an `AcmeCertProvider`
- [ ] Integration with `TlsAcceptor::bind_acme()` or similar constructor
- [ ] Unit tests for ACME config construction (challenge responder setup)
- [ ] Integration test: ACME cert provisioning with Let's Encrypt staging (marked `#[ignore]` for CI)
## References
- docs/architecture/server.md — TLS certificate provisioning modes
- docs/architecture/decisions/008-acme-lets-encrypt.md — ACME design, rustls-acme choice
- docs/architecture/transport.md — feature flags, TLS transport constraints
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,56 @@
---
id: transport/iroh-transport
name: Implement IrohTransport and IrohAcceptor (feature-gated iroh)
status: pending
depends_on:
- transport/trait-and-types
- transport/tcp-transport
scope: moderate
risk: high
impact: component
level: implementation
---
## Description
Implement iroh QUIC P2P transport. Per ADR-003, use `tokio::io::join(recv_stream, send_stream)` to combine iroh's split QUIC streams into a single duplex stream that russh can consume.
Client-side: `IrohTransport` connects to a remote iroh endpoint, opens a bidirectional QUIC stream, and joins the halves.
Server-side: `IrohAcceptor` creates an iroh endpoint, accepts incoming connections, accepts bidirectional streams.
iroh supports proxy configuration natively via `Endpoint::builder()`, which is how `--proxy` works with iroh transport (ADR-010).
Feature-gated behind `iroh` feature flag.
## Acceptance Criteria
- [ ] `crates/wraith-core/src/transport/iroh.rs` (behind `#[cfg(feature = "iroh")]`)
- [ ] `IrohTransport` holds: target endpoint ID (base58-decoded to `NodeId`), relay URL, optional proxy URL
- [ ] `IrohTransport::connect()` calls `endpoint.connect(node_id, alpn)`, then `conn.open_bi()`, then `tokio::io::join(recv, send)`
- [ ] ALPN value is `b"wraith-ssh"`
- [ ] `IrohTransport::describe()` returns e.g. `"iroh://<endpoint-id>"`
- [ ] `IrohAcceptor` holds an `iroh::Endpoint` instance
- [ ] `IrohAcceptor::bind()` creates endpoint with relay URL and optional proxy config
- [ ] `IrohAcceptor::accept()` calls `endpoint.accept()`, then `conn.accept_bi()`, then `tokio::io::join(recv, send)`
- [ ] `IrohAcceptor` exposes `endpoint_id()` returning base58-encoded node ID for CLI display
- [ ] Default relay is n0's `https://relay.iroh.network/` (ADR-009)
- [ ] Proxy URL passed to `Endpoint::builder()` for outbound proxy (ADR-010)
- [ ] `TransportInfo.transport_kind` is `TransportKind::Iroh { endpoint_id }`
- [ ] Module re-exported from `transport/mod.rs` behind `#[cfg(feature = "iroh")]`
- [ ] Unit tests: endpoint creation, stream join produces correct type
- [ ] Integration test: iroh client connects to iroh server, stream is duplex (may need iroh relay, mark `#[ignore]` for CI)
## References
- docs/architecture/transport.md — IrohTransport row, iroh stream join, relay config
- docs/architecture/decisions/003-iroh-stream-join.md — tokio::io::join rationale
- docs/architecture/decisions/009-default-iroh-relay.md — default relay
- docs/architecture/decisions/010-transport-chaining-cli.md — proxy configuration
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,41 @@
---
id: transport/tcp-transport
name: Implement TcpTransport and TcpAcceptor
status: pending
depends_on:
- transport/trait-and-types
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement the simplest transport: plain TCP. `TcpTransport` connects via `TcpStream::connect(addr)` on the client side. `TcpAcceptor` accepts via `TcpListener::accept()` on the server side. This is the baseline transport that all others build upon conceptually.
## Acceptance Criteria
- [ ] `crates/wraith-core/src/transport/tcp.rs` exports `TcpTransport` and `TcpAcceptor`
- [ ] `TcpTransport` holds a `SocketAddr` target address
- [ ] `TcpTransport::connect()` calls `TcpStream::connect(addr)` and returns the stream
- [ ] `TcpTransport::describe()` returns e.g. `"tcp://1.2.3.4:22"`
- [ ] `TcpAcceptor` holds a `TcpListener` and accept address
- [ ] `TcpAcceptor::accept()` calls `listener.accept()`, returns `(stream, TransportInfo)` with `remote_addr` set and `TransportKind::Tcp`
- [ ] `TcpAcceptor` constructor binds the listener: `TcpAcceptor::bind(addr)` async factory
- [ ] Connection timeout handling (tokio default connect timeout is reasonable; document behavior)
- [ ] Unit tests: connect creates a stream, accept receives a connection, describe format
- [ ] Integration test: client connects to server via TCP, stream is duplex
## References
- docs/architecture/transport.md — TcpTransport row in implementations table
- docs/architecture/overview.md — "TCP on port 22 for basic use"
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,54 @@
---
id: transport/tls-transport
name: Implement TlsTransport and TlsAcceptor (feature-gated tls)
status: pending
depends_on:
- transport/tcp-transport
- transport/trait-and-types
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Implement TLS transport that wraps TCP with `tokio-rustls`. Client-side: `TlsTransport` establishes a TCP connection and wraps it with a TLS client session. Server-side: `TlsAcceptor` accepts TCP connections and wraps them with a TLS server session.
Supports:
- Manual cert/key configuration (`--tls-cert`, `--tls-key`)
- insecure mode (accept self-signed certs) for development
- `tls_server_name` override for SNI (ADR-010)
- Stealth mode support requires peeking at first bytes post-TLS-handshake (handled in server task, but TLS stream must support this)
Feature-gated behind `tls` feature flag.
## Acceptance Criteria
- [ ] `crates/wraith-core/src/transport/tls.rs` (behind `#[cfg(feature = "tls")]`)
- [ ] `TlsTransport` holds: target addr, optional `tls_server_name`, `insecure` flag, optional root cert for verification
- [ ] `TlsTransport::connect()` does TCP connect then TLS client handshake via `tokio_rustls::TlsConnector`
- [ ] When `insecure`, accepts any certificate (dangerous, `webpki_roots::CertStore` bypass or custom verifier)
- [ ] When not `insecure`, verifies server cert against system roots + optional custom CA
- [ ] `TlsTransport::describe()` returns e.g. `"tls://example.com:443"`
- [ ] `TlsAcceptor` holds: `TcpListener`, `ServerConfig` (from `rustls::ServerConfig`)
- [ ] `TlsAcceptor::accept()` does TCP accept then TLS server handshake via `tokio_rustls::TlsAcceptor`
- [ ] `TlsAcceptor` constructor accepts: `tls_cert` path/data, `tls_key` path/data, optional ACME config (stub for now)
- [ ] `TransportInfo.transport_kind` is `TransportKind::Tls { server_name }`
- [ ] Module re-exported from `transport/mod.rs` behind `#[cfg(feature = "tls")]`
- [ ] Unit tests for connect/accept with self-signed certs (insecure mode)
- [ ] Integration test: full TLS client-to-server connection succeeds
## References
- docs/architecture/transport.md — TlsTransport row, TLS cert provisioning
- docs/architecture/server.md — TLS certificate provisioning modes
- docs/architecture/decisions/008-acme-lets-encrypt.md — ACME cert support (feature-gated)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,44 @@
---
id: transport/trait-and-types
name: Define Transport trait, TransportAcceptor trait, TransportInfo, and TransportKind types
status: pending
depends_on:
- setup/project-init
scope: narrow
risk: low
impact: phase
level: implementation
---
## Description
Define the core transport abstraction types that everything else builds on. This is the foundation per ADR-001: a `Transport` trait that produces `AsyncRead + AsyncWrite + Unpin + Send` streams, and a `TransportAcceptor` trait for the server side.
The `TransportInfo` and `TransportKind` types carry metadata about incoming connections (remote address, transport kind) which the server handler needs for logging and auth decisions.
## Acceptance Criteria
- [ ] `crates/wraith-core/src/transport/mod.rs` exports `Transport` trait, `TransportAcceptor` trait, `TransportInfo`, `TransportKind`
- [ ] `Transport` trait: `async fn connect(&self) -> Result<Self::Stream>` where `Self::Stream: AsyncRead + AsyncWrite + Unpin + Send + 'static`
- [ ] `Transport::describe(&self) -> String` for human-readable logging
- [ ] `TransportAcceptor` trait: `async fn accept(&self) -> Result<(Self::Stream, TransportInfo)>` with same stream bounds
- [ ] `TransportInfo { remote_addr: Option<SocketAddr>, transport_kind: TransportKind }`
- [ ] `TransportKind` enum: `Tcp`, `Tls { server_name: Option<String> }`, `Iroh { endpoint_id: String }`
- [ ] Traits are `Send + Sync + 'static`
- [ ] Re-exported from `crates/wraith-core/src/lib.rs`
- [ ] Unit tests verifying trait objects can be constructed (trait is object-safe with `Box<dyn Transport<Stream = ...>>`)
- [ ] Documentation comments on all public types referencing ADR-001, ADR-004
## References
- docs/architecture/transport.md — Transport trait, TransportAcceptor trait, TransportInfo, TransportKind definitions
- docs/architecture/decisions/001-pluggable-transport.md — pluggable transport rationale
- docs/architecture/decisions/004-ssh-over-transport.md — SSH runs over transport
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion