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.
4.6 KiB
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 |
|
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 theServeConfig; construction (SshAdapter::new) loads the host keys decrypt-at-construction via the injectedHostKeySource(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 theKeySourceErrorvariant (never a half-configured adapter, never a keyless server that fails later).- russh
Configconstruction (ADR-002 surface notes) — built from the serve-config:methods={publickey}exactly (MethodSetfrom[MethodKind::Publickey]— honest advertisement, ADR-004 decision 4);keysfrom the loaded host-key set;window_size/maximum_packet_size/channel_buffer_size/max_auth_attemptsmapped verbatim fromLimits;inactivity_timeoutmapped always-Some(the bounded-resources posture — the crate never constructs a session with no wall clock); russh's rekeyLimitsstay russh-enforced at their RFC-4253 defaults (recorded in the wave-1 config notes — keep that recording).server_idstays 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 viarussh::server::run_stream(edge termination only — no ALPN code, no relay leg; thealk/sshpin is a ledger entry, ADR-001 — nothing here speaks it). The exact return shape (awaitedRunningSession, 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::Handlerimpl with the adapter'stype 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 →Errwith theKeySourceErrorvariant preserved, no adapter constructed- russh
Configbuilt from the serve-config:methodsis exactly{publickey}; host keys rideConfig::keys; theLimitsknobs map one-for-one withinactivity_timeoutalwaysSome SshAdapter::handlemounts 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 --checkpass
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