--- status: accepted last_updated: 2026-09-10 --- # ADR-006: Module layout and test surface ## Status Accepted (2026-09-10) ## Context The extracted crate is organized as four modules plus the error (`lib.rs`, `server.rs`, `client.rs`, `pem.rs`, `signing.rs`) with all tests as in-module `#[cfg(test)]` blocks. alktls adds three modules of moved types (ADR-005) and, per the repo's conventions, needs a decided module map and test layout before the port starts. Two shape questions need answers: where the moved types live relative to the config constructors, and how the seed tests port (no external workspace exists here, so in-module tests are the only cross-module surface a full-crate crate has). ## Decision **Module layout** (one module per file, re-exported from `lib.rs`): ``` src/ ├── lib.rs — crate docs, TlsError (ADR-002), re-exports ├── identity.rs — TlsIdentity, Ed25519SecretKey, AcmeDirectory (ADR-005) ├── credentials.rs — ConnectionCredentials, RemoteIdentity (ADR-005) ├── fingerprint.rs — fingerprint_from_cert_der, extract_ed25519_raw_key_from_spki, │ DER parser (ADR-005) ├── server.rs — TlsServerConfig, build_rustls_server_config, │ RawKeyCertResolver, AcceptAnyCertVerifier, │ SelfSignedCert / generate_self_signed_cert ├── client.rs — TlsClientConfig, select_server_verifier, │ build_client_auth, RawKeyClientCertResolver, │ NoClientCertResolver, FingerprintPinVerifier, │ load_platform_root_cert_store ├── pem.rs — load_cert_chain, load_private_key └── signing.rs — Ed25519SigningKey (rustls SigningKey + Signer) ``` Public API surface is `lib.rs` re-exports only (AGENTS.md convention 12); modules are `pub` for discoverability but the re-export block is the documented surface. **Feature gates stay on types and accessors, not modules.** The `noq` feature gates `for_noq()` + `NoqWrap`; the `tcp` feature gates `for_tcp_tls()`; the `acme` feature gates the ACME branch of `TlsServerConfig::new` + the spawned task + the `futures` dep. Unlike the alktunnels/alktty backends, there are no substrate modules to gate — the transport-specific code is accessor-shaped. **Test surface:** - Port the extracted crate's in-module tests as the seed (they assert the behavior-preservation invariants: `max_early_data_size` on the server paths, `enable_early_data` on the client config, the nine-scheme list, resolver behavior, PEM error paths, signing). - Add `tests/` integration tests for the cross-module surfaces a single crate has: `TlsServerConfig::new` → `for_noq()` / `for_tcp_tls()` round-trips per identity variant; `TlsClientConfig` verifier selection matrix (`Some`/`None` × raw-key/X.509); the root-store fallback against an empty platform store; `acme-tls/1` append on the ACME path only. - Pin the exact nine-scheme list in an integration test (Phase 0 OQ-TLS-05's regression-proof shape — the extracted in-module test only checks membership of two schemes), and pin `enable_early_data = true` on the client config (the client half of the 0-RTT invariant). - `TlsIdentity::Acme`'s `unreachable!` in `build_rustls_server_config` becomes a config error (the extracted code relies on an internal dispatch invariant; the port replaces the `unreachable!` with the `AcmeConfig` variant, keeping no-panics-in-library-code). ## Consequences **Positive:** - The port is mechanically guided: module map + seed tests make the Phase 2 port a diff against the extracted source. - The invariant assertions move from "documented" to "tested" at both unit and integration level. **Negative:** - Integration tests that wrap real QUIC endpoints are out of scope (they belong to transport crates); the seam tests assert config construction, not handshakes — the scope boundary holds. ## References - AGENTS.md conventions 1, 9, 12 — comments, feature gates, module structure - `docs/research/phase-0.md` §Gaps #5, OQ-TLS-05 — the test-surface question - ADR-002 — `TlsError` (no `Config(String)` catch-all) - ADR-005 — the moved modules