Files
alkssh/tasks/implementation/w2-server-mount.md
T
glm-5.3-flash 18ef55c60d docs(tasks): decompose wave 2 — the server stack
Six implementation tasks + the gate under tasks/implementation/,
chained sequentially (the auth hooks, dispatch, and channel cap all
edit the same per-connection Handler impl):

- w2-server-mount: SshAdapter::handle mount + russh Config
  construction from the serve-config (honest advertisement, host-key
  decrypt-at-construction, the Limits knob mapping) + the Handler
  skeleton at fail-closed defaults
- w2-auth-bridge: the ADR-004 dispositions (ed25519-only explicit
  decline, fingerprint-only resolution, explicit declines for every
  non-publickey method)
- w2-loopback-harness: the in-process client/server contract-test
  vehicle over the wave-1 doubles
- w2-dispatch-decline-core: parse-only dispatch + the fail-closed
  decline core, contract-tested per feature build
- w2-spawn-return: the structural constraint pinned by test + the
  exit-reporting mechanics via the cloned Session::handle()
- w2-bounded-resources: the in-crate concurrent-channel cap at the
  handler surface
- review-wave-2: the gate

The plan carries the wave-2 decomposition record with four
interpretations flagged for the gate (skeleton-level git probe,
declining routing stubs, the adapter error surface, the harness
transport choice). taskgraph validate: 19 tasks, clean.
2026-10-28 07:38:04 +00:00

4.6 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
w2-server-mount Wave 2 — SshAdapter::handle mount + russh Config construction pending
review-wave-1
w1-config
w1-key-sources
w1-error-model
moderate medium project implementation

Description

Land the door frame: the SshAdapter type — the ADR-001 mount point — and the russh server Config construction from the wave-1 ServeConfig. This is the substrate every mapping composes into; the auth bridge and dispatch fill the Handler this task skeletons.

  • SshAdapter — holds the ServeConfig; construction (SshAdapter::new) loads the host keys decrypt-at-construction via the injected HostKeySource (ADR-005 decision 6): a locked / absent / decryption-failed / unknown-version load means no server is constructed — the error surfaces as a distinct adapter construction error wrapping the KeySourceError variant (never a half-configured adapter, never a keyless server that fails later).
  • russh Config construction (ADR-002 surface notes) — built from the serve-config: methods = {publickey} exactly (MethodSet from [MethodKind::Publickey] — honest advertisement, ADR-004 decision 4); keys from the loaded host-key set; window_size / maximum_packet_size / channel_buffer_size / max_auth_attempts mapped verbatim from Limits; inactivity_timeout mapped always-Some (the bounded-resources posture — the crate never constructs a session with no wall clock); russh's rekey Limits stay russh-enforced at their RFC-4253 defaults (recorded in the wave-1 config notes — keep that recording). server_id stays russh's default posture unless the task finds a family reason otherwise (decide and record).
  • The mount — SshAdapter::handle(stream): a generic stream in, the full russh server stack per stream via russh::server::run_stream (edge termination only — no ALPN code, no relay leg; the alk/ssh pin is a ledger entry, ADR-001 — nothing here speaks it). The exact return shape (awaited RunningSession, an internal spawn + join handle, …) is decided here against the spawn-and-return posture (ADR-006 decision 4) and the assembly's mounting needs — decide and record; it is semver-visible once consumers exist.
  • The Handler skeleton — the per-connection russh::server::Handler impl with the adapter's type Error (From<russh::Error>, thiserror — the wave-2-shaped error surface the wave-1 error-model note reserved); every hook left at its fail-closed russh default (decline/reject). Later wave-2 tasks fill the auth hooks (w2-auth-bridge) and the channel/request dispatch (w2-dispatch-decline-core).

No mapping seams, no feature-gated serving (wave 3); no client dial (wave 4). The feature gates stay untouched — nothing consumes them yet (the dispatch task is their first cfg consumer).

Acceptance Criteria

  • SshAdapter::new(ServeConfig) loads host keys decrypt-at-construction; locked / absent / decryption-failure / unknown-version → Err with the KeySourceError variant preserved, no adapter constructed
  • russh Config built from the serve-config: methods is exactly {publickey}; host keys ride Config::keys; the Limits knobs map one-for-one with inactivity_timeout always Some
  • SshAdapter::handle mounts the full russh server stack per generic stream (run_stream); no ALPN/relay code exists
  • The Handler skeleton compiles with an adapter error type (From<russh::Error>); every hook at its fail-closed default
  • Tests: honest advertisement (methods exactly {publickey}); the knob mapping; construction failure postures (locked vault → no server, absent host keys → no server)
  • cargo test, cargo clippy --all-targets -- -D warnings, cargo fmt --check pass

References

  • docs/architecture/decisions/001-alkssh-alpn-pin.md (the mount-point contract: SshAdapter::handle, raw vanilla-SSH stream in)
  • docs/architecture/decisions/002-russh-pin.md (the Config surface notes; honest advertisement)
  • docs/architecture/decisions/004-auth-dispositions.md (decision 4 — methods = {publickey})
  • docs/architecture/decisions/005-at-rest-key-custody.md (decrypt-at-construction; locked → no server)
  • docs/architecture/overview.md §Bounded-resources policy
  • docs/plans/implementation.md §Wave 2 (first bullet)
  • russh 0.65.0 in-source: server::Config, server::run_stream, auth::MethodSet

Notes

To be filled by implementation agent

Summary

To be filled on completion