--- id: w1-config name: "Wave 1: the config surface (serve-config, dial-config, Limits)" status: completed depends_on: [w1-manifest, w1-key-sources, w1-host-key-policy] scope: moderate risk: low impact: project level: implementation --- ## Description Land the pure configuration types both directions implement against, the shapes every later wave's constructors take: - **Serve-config** (server direction, `mapping.md`/`overview.md`), the assembly-supplied serving configuration: the OQ-08 fields (**shell argv** and **exec wrapper argv**, each `Option>`; when unset, its row declines with a clean error, never a fallback, no crate default; the wrapper form is platform-neutral), the `IdentityProvider` handle (`Arc`, which the assembly injects; the crate never resolves identities itself), the host-key source handle (the wave-1 key-source seam), the tunnel `ChannelClient` handle (the route-1 bridge's family-fabric entry, held as an `Option`; when absent, tunnel rows decline even under the feature), and the `Limits`. `cwd`/`env` in `NegotiateRequest` are assembly-supplied serve-config values, never wire-derived (OQ-09 posture). - **Dial-config** (client direction, `client.md` §The dial surface): address, key source (the wave-1 seam; never static keys, never env vars), the `HostKeyPolicy` handle, and the bounded-resource knobs: `keepalive_interval`/`keepalive_max` (15 s / 3 default posture per the reverse-runner evidence), `inactivity_timeout`, window/packet knobs. - **`Limits`**: the bounded-resources budget model (`overview.md`'s table): the in-crate max-concurrent-channels cap, exec-parse length caps (consumed by the wave-3 grammar), and the russh knob mapping with documented default postures (`max_auth_attempts` default 10, sized for the counting `none`-probe per ADR-004 decision 6; `window_size`/`maximum_packet_size`/`channel_buffer_size` set; `inactivity_timeout` set, assembly-tunable). Every session carries it; no unbounded loops, no unbounded buffers. Types only: no russh `Config` construction (wave 2), no dialing (wave 4). The OQ-08 unset-declines semantics live in the type shape (`Option` fields + doc contracts), not in construction-time validation. ## Acceptance Criteria - [x] Serve-config carries the OQ-08 fields as `Option` with unset-declines doc contracts; no crate default shell/wrapper - [x] `IdentityProvider`, host-key source, and `ChannelClient` are injected handles; the crate holds no identity/trust policy of its own - [x] Dial-config carries key source + `HostKeyPolicy` + the keepalive/inactivity/window knobs with the documented defaults - [x] `Limits` carries the in-crate caps (channels, exec-parse) and the russh knob defaults (`max_auth_attempts` 10, keepalive 15 s / 3 client posture) - [x] Unit tests cover default construction and the `Option`-field semantics - [x] `cargo test`, `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check` pass ## References - docs/architecture/overview.md §Public surface, §Bounded-resources policy - docs/architecture/mapping.md (OQ-08 rows, the data-plane/limits section) - docs/architecture/client.md §The dial surface - docs/architecture/open-questions.md (OQ-08, OQ-09 resolutions) - docs/architecture/decisions/004-auth-dispositions.md (decision 6: the `max_auth_attempts` default) ## Notes > Landed `src/config.rs` (`ServeConfig`, `DialConfig`, `Limits`, > four posture constants) + lib.rs re-exports. Design decisions > pinned against the docs and the alkcall/russh 0.65 source: > > - **`Limits` is the single budget model both directions carry** > (`Copy`, plain tuning data): the in-crate caps > (`max_concurrent_channels` 64, `max_exec_command_len` 8192, with > numbers chosen here and documented as assembly-tunable posture > constants) plus the russh-knob mapping (`max_auth_attempts` 10 > per ADR-004 decision 6; `window_size` 2 MiB / `maximum_packet_size` > 32 KiB / `channel_buffer_size` 100, russh's own defaults kept; > `inactivity_timeout` 600 s carried as plain `Duration` so the > "set (assembly-tunable)" posture is structural, with wave 2/4 > mapping it to russh's `Option` as always-`Some`; > `keepalive_interval`/`keepalive_max` Some(15 s)/3 riding inside as > the client-direction posture, satisfying the dial-config > criterion without a duplicate knob set). russh's rekey `Limits` > stay russh-enforced at their RFC-4253 defaults and are not > carried (documented; wave-2 material). > - **Dial-config carries the knobs via its `Limits` field**: the > criterion's "keepalive/inactivity/window knobs with the > documented defaults" read as the budget model riding the config; > the accepted criterion on `Limits` itself pins that placement, > so a second knob set on `DialConfig` would have duplicated state. > - **No `Default` for the two configs**: the required handles > (`Arc`, > `Arc`, `Arc`, > `Arc`) have no crate-defaultable shape > (identity resolution and trust are injection-only, per the > criterion); `ServeConfig::new`/`DialConfig::new` take them, and > every tunable is a `pub` field + `Limits::default()`. > - **The OQ-08 fields are plain `Option>` `pub` fields > + doc contracts**; no construction-time validation (the > `error.rs` no-config-error contract). Two request-time check > helpers exist (`shell_configured`/`exec_wrapper_configured`, > plus `tunnel_configured`) for the wave-2 dispatch to call, > reading the `Option`, never validating. > - **`cwd`/`env` (OQ-09 posture) carried on `ServeConfig`** as > `Option` / `BTreeMap` (empty defaults; > never wire-derived; doc contract). > - **`channel_client` is not feature-gated**: alkcall is a base > dependency and a field presence is not a capability; the `tunnel` > feature gate applies at wave 2 on top (absent → decline even > under the feature; unfeatured → decline too). Doc contract > records both gates. > - Verified the alkcall 0.10 surfaces in-source (registry): > `core::auth::IdentityProvider` is a sync, > `Send + Sync + 'static` trait (dyn-compatible with no > async-trait help on alkssh's side; `ChannelClient` and the > `NoopIdentityProvider` test fixture ride > `alkcall::channels::client` / `alkcall::core::auth`), and the > russh 0.65.0 knob names (`window_size`, `maximum_packet_size`, > `channel_buffer_size`, `inactivity_timeout`, > `keepalive_interval`, `keepalive_max`, `max_auth_attempts`) match > this module's fields one-for-one. ## Summary > Config surface landed: `src/config.rs` with `Limits` (in-crate > caps: `max_concurrent_channels` 64, `max_exec_command_len` 8192; > russh knobs: `max_auth_attempts` 10, window 2 MiB, packet 32 KiB, > buffer 100, inactivity 600 s, keepalive Some(15 s)/3, with every > number > a named posture constant or documented ADR anchor), `ServeConfig` > (OQ-08 `shell_argv`/`exec_wrapper_argv` as unset-declines > `Option>`, OQ-09 `session_cwd`/`session_env`, > injected `IdentityProvider` + `HostKeySource` + optional > `ChannelClient`, `limits`), and `DialConfig` (address, > injected `KeySource` + `HostKeyPolicy`, `limits` with the > keepalive/inactivity/window posture riding inside). No > construction-time validation, no crate defaults for the unset > rows, no `Default` for the configs (handles only). Tests: defaults > carry the documented postures; `Option` fields flip independently; > handles hold by `Arc::ptr_eq`; the dial's key source and policy > stay fail-closed through the config. Verified: `cargo test` (36), > `cargo test --all-features`, `cargo clippy --all-targets > -- -D warnings`, `cargo clippy --all-features --all-targets > -- -D warnings`, `cargo fmt --check` all pass; `taskgraph > validate` clean.