Files
alkssh/docs/architecture/mapping.md
T
glm-5.3-flash 263f91fdb7 docs(architecture): review-pass cleanup — inconsistencies, stale refs, scaffolding refresh
- OQ-02: rewritten as a clean resolved statement; contradictory
  deferred-table row removed
- OQ-05: 'old-code anti-pattern' framing corrected — ephemeral-fleet
  accept-and-continue is a legitimate case-2 HostKeyPolicy; the crate
  rule is 'no hard-coded trust model', not 'accept-all forbidden';
  dangling 'expected ADR-007' pointer replaced with the real contract
  locations (auth.md, client.md)
- README: OQ counts corrected (6 resolved / 3 open / 4 deferred);
  sibling references normalized to real ADR IDs (alkcall 010 is alkhttp
  014's analog; 'ADR-016-analog' was a wrong number)
- client/auth: OQ-07 status corrected to resolved-at-adapter-level;
  TOFO typo; TOFU framing
- ADR-004: dangling 'ADR-008' -> serve-config defaults
- ADR-006: SOCKS5 typo
- mapping: alktty typo; '(OQ-… note below)' placeholder -> the real
  POC-3 note reference
- research: cross-repo numbers qualified (alktty OQ-44, alkvault
  ADR-021); reverse-runner lesson 5 reframed per correction
- scaffolding: Cargo.toml description/comments and src/lib.rs doc
  comment updated against ADR-002/003/006 (dynamic-forward retired,
  git exec-only, russh pin decided); CHANGELOG logs POC completion and
  ADRs 001-006

Verification: cargo test, cargo clippy --all-targets -- -D warnings,
cargo fmt --check, cargo check — all pass
2026-10-27 10:00:11 +00:00

9.3 KiB
Raw Blame History

status, last_updated
status last_updated
draft 2026-10-27

Mapping — server direction: SSH channels × family seams (the spine)

The door framing

The server direction is a door for vanilla clients: vanilla in, family out. Vanilla ssh/git connect as they would to any sshd; every accepted channel/request maps to exactly one family protocol seam and exactly one cargo feature (ADR-003). Rows below are POC-3-validated against published sibling crates with real vanilla tooling (OpenSSH 9.6, git 2.43, curl 8.5 — poc-3-mapping-bakeoff.md).

The seams

Family crate Serve-over-a-stream entry Signature shape
alktty drive_session[_pre_negotiated] (impl AsyncWrite, impl AsyncRead, backends, identity)
alkgit serve_duplex_resolved (repo_id, identity, service, duplex, limits, backend traits)
alktunnels — no serve entry (absence note resolved: route 1 never needed one, ADR-006) consumer/open-op registrations

russh channels convert to duplex via into_stream/split — the bridge primitives every mapping uses (POC-1 §7).

Channel-type rows

SSH channel type Vanilla use Feature Family seam Contract notes
session → pty_request + shell_request ssh host interactive shell session alktty: NegotiateRequest constructed locally → drive_session_pre_negotiated pty_request args → TerminalParamsWire (term/cols/rows/pixels; SSH modes → the reserved modes JSON); shell convention per OQ-08; the data plane is a translation bridge, not a byte pump — alktty is chunk-framed both directions (stdin/stdout/stderr/CTRL chunks): SSH data→stdin chunks; stdout chunks→SSH data; stderr chunks→extended_data(1); exit chunk→exit-status/exit-signal + close (POC-3 §D.1)
session → exec_request (non-git) ssh host "cmd" session alktty pipe mode (terminal: None) — separate stderr stderr rides extended_data(1) — only valid without PTY; PTY mode merges (kernel PTY, POC-3 §D.1 proven)
session → exec_request git git clone/push ssh:// git alkgit: parse command → Service + repo → serve_duplex_resolved(repo_id, identity, service, duplex, …) exec-only (ADR-006 decision 2): the repo rides inside the exec command string (git-upload-pack '/repo'); vanilla git never speaks subsystem for git; process-mode dialect (server-speaks-first advertisement); exit-status proven both directions
session → subsystem_request sftp clients; manual ssh -s sftp (placeholder) / git rows — sftp name: declines until alkfs lands (OQ-13) — the feature exists, the route doesn't. git service names: decline with reason — a subsystem request carries only a name, never a repo id (POC-3 §A.3); never half-serves
direct-tcpip ssh -L; all ssh -D traffic tunnel alktunnels route 1: adapter-side bridge — TunnelSession::open_direct on a held ChannelClient, pump take_halves() vs the channel target = SubstrateAddr::HostPort/Path; Substrate = Tcp/Unix; accept only after the fabric open succeeds — failure maps to an explicit ConnectFailed reject (POC-3 §C.2); vanilla -D's dynamic targets dispatch here (POC-3 correction 2)
forwarded-tcpip ssh -R data channels tunnel the register_tunnel_forwarded_openable's ForwardedHandler shape — hand the channel duplex to the handler listen flow = the global-request rows below; the data leg is untested in POC-3 beyond refusal (see the POC-3 note below)
direct-streamlocal unix-socket forward tunnel SubstrateAddr::Path treatment unix-only; decline shape per OQ-11
x11 X forwarding — (never) — always declines; not feature-gated (ADR-003 §5)
auth-agent agent forwarding — (never) — always declines; the anti-pattern of the alkvault custody posture (ADR-003 §5)
session → SOCKS5-in-channel (no vanilla use) — (retired, ADR-003 §1a) — vanilla -D is client-side SOCKS + direct-tcpip; the family local door is alksocks composed at the assembly — see client.md

Session-channel dispatch (ADR-006, as amended)

Parse-only. A session channel that receives a request takes the parse path: exec_request parses first (a git-parseable command routes to git; anything else to session's pipe path), and pty_request/shell_request route to session. Request-less session channels decline as unfeatured. Parsing happens before dispatch and before any resource allocation — a leaked channel alloc is a capability leak, so decline first, alloc never (POC-3 stage A's decline matrix is the contract: unknown repo → collapsed generic denial before any protocol byte — no existence oracle; non-git command → parse-then-fail).

Global-request rows (the -R listen flow)

Handler::tcpip_forward(address, &mut port) -> bool + Handler::streamlocal_forward — gate on tunnel (decline both the listen request and the data channels when absent). The bound-port out-param (port = 0 → server picks) is a spec detail in client.md's lifecycle primitives; disposition follows tunnel. POC-3 note: only the refusal shape was exercised (egress-only build); the served listen flow is implementation-phase contract-test territory.

Request-level rows on a session channel

SSH request Family mapping
pty_request NegotiateRequest.tty = TerminalParamsWire{…} (POC-3 §D.1: live stty size honored)
shell_request NegotiateRequest.cmd per OQ-08 (assembly convention, never wire-supplied)
exec_request parse → git dispatch (git) or alktty pipe cmd (session); unknown → channel_failure, no resource alloc
subsystem_request decline (per the rows table; honest, with reason)
env_request decline by default (OQ-09 deferred); wire env is not family config
window_change_request resize control (POC-3 §D.1: live resize proven)
signal signal control (alktty names verbatim); PTY mode reaches the process group, pipe mode is pid-only (backend semantics, recorded in OQ-10)
data / extended_data(1) the translation bridge (rows table)
exit_status from the alktty exit chunk — sent before close, from the spawned task via Session::handle() (POC-3 §D: proven both legs)
exit_signal only for signal-terminated exits; OQ-10 (chunk carries no signal name — alktty ask + assembly fallback pending)

The structural constraint: spawn-and-return

russh (this pin: ADR-002) runs server handler callbacks inline inside the session's packet loop — a blocking handler suspends all packet routing for the connection (channel data is routed by that same loop; an inline serve deadlocks against the peer's immediate follow-up bytes — POC-3 proved by construction with receive-pack). Therefore: every serving path is spawned; handlers return fast; exit/status reporting from spawned tasks rides the cloned Session::handle(). This is a load-bearing correctness constraint of the whole spine, not an implementation choice.

Data-plane / limits

  • russh knobs (window_size, maximum_packet_size, channel_buffer_size, Limits, inactivity_timeout, keepalive_*) + in-crate max-concurrent-channels cap + per-length exec-parse limits — per the bounded-resources policy in overview.md.
  • alkcall bridge leg (when the family half is remote): channels-layer reassembly cap 1 MiB/channel; ADR-034 grants alk/ssh stream_types [0, 1] because SSH multiplexes internally.
  • Honest advertisement: Config::methods mirrors implemented auth (ADR-004); an unfeatured channel type declines at open, never half-opens; vanilla clients observe clean diagnostics (POC-1 §5: channel-scoped decline, auth intact, no hangs).

Decline matrix (per feature build, contract-tested)

Build Serves Declines
none (default) — everything
git git-parseable exec pty/shell, plain exec, all other channel types, all session requests
session pty/shell/pipe-exec git-parseable exec (the git parse is its gate), other channel types
tunnel direct/forwarded-tcpip, streamlocal (unix), -R listens session channels (auto-reject), all others
any + sftp slot — sftp subsystems decline-with-reason until alkfs

Every row is tested in every build it can appear in — the decline paths are contract territory (AGENTS.md conventions 5–6), including under --all-features.

Open Questions

  • OQ-08 (shell convention — status: open): blocks this doc's shell_request/NegotiateRequest.cmd row detail.
  • OQ-10 (exit-chunk signal identity — status: open): blocks the exit_signal row's precise encoding; the fallback direction is recorded there.
  • OQ-11 (streamlocal platform decline shape — status: open).
  • OQ-02 (dispatch composition — resolved by ADR-006, as amended).

References

  • ADR-003 (features), ADR-006 (route 1, exec-only git, dispatch), ADR-001 (the ALPN/edge context), ADR-004 (auth)
  • docs/research/mapping-table.md (the validated research ancestor + annotations), poc-3-mapping-bakeoff.md (stages A–D), poc-1-auth-bridge.md §5/§7 (decline observability, plumbing)
  • Family ADR-003; alkcall ADR-034 (alk/ssh stream_types)
  • client.md (the same door, client-side)