Files
alkssh/tasks/implementation/w1-config.md
T
glm-5.3-flash 949d0bbbe4 feat(config): land the configuration surface (serve-config, dial-config, Limits)
- ServeConfig: OQ-08 shell_argv / exec_wrapper_argv as
  Option<Vec<String>> with unset-declines doc contracts (no crate
  default shell/wrapper); OQ-09 session_cwd/session_env
  (assembly-supplied, never wire-derived); injected
  Arc<dyn IdentityProvider> + Arc<dyn HostKeySource> handles;
  optional Arc<ChannelClient> route-1 handle (absent -> tunnel rows
  decline even under the tunnel feature); Limits.
- DialConfig: address + injected Arc<dyn KeySource> and
  Arc<dyn HostKeyPolicy> handles + Limits.
- Limits: the bounded-resources budget model both directions carry —
  in-crate caps (max_concurrent_channels 64, max_exec_command_len
  8192) and the russh-knob mapping with documented postures
  (max_auth_attempts 10 per ADR-004 decision 6; window 2 MiB /
  packet 32 KiB / buffer 100; inactivity 600 s always-set shape;
  keepalive Some(15 s)/3 client posture).
- Types only: no russh Config construction (wave 2), no dialing
  (wave 4); no construction-time validation — unset-declines lives
  in the type shape (error.rs no-config-error contract).

Verified: cargo test (36), cargo test --all-features, clippy
(all-targets, -D warnings, default + all-features), fmt --check.
2026-10-28 06:57:52 +00:00

7.8 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
w1-config Wave 1 — the config surface (serve-config, dial-config, Limits) completed
w1-manifest
w1-key-sources
w1-host-key-policy
moderate low project 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<Vec<String>> — unset → its row declines with a clean error, never a fallback, no crate default; the wrapper form is platform-neutral), the IdentityProvider handle (Arc<dyn IdentityProvider> — the assembly injects it; 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 — Option, 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, 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

  • Serve-config carries the OQ-08 fields as Option with unset-declines doc contracts; no crate default shell/wrapper
  • IdentityProvider, host-key source, and ChannelClient are injected handles; the crate holds no identity/trust policy of its own
  • Dial-config carries key source + HostKeyPolicy + the keepalive/inactivity/window knobs with the documented defaults
  • Limits carries the in-crate caps (channels, exec-parse) and the russh knob defaults (max_auth_attempts 10, keepalive 15 s / 3 client posture)
  • Unit tests cover default construction and the Option-field semantics
  • 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 — numbers chosen here, 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 — wave 2/4 map 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<dyn IdentityProvider>, Arc<dyn HostKeySource>, Arc<dyn KeySource>, Arc<dyn HostKeyPolicy>) 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<Vec<String>> 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<PathBuf> / BTreeMap<String, String> (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 — every number a named posture constant or documented ADR anchor), ServeConfig (OQ-08 shell_argv/exec_wrapper_argv as unset-declines Option<Vec<String>>, 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.