Files
glm-5.3-flash f4b5f896b4 docs: sweep em dashes from all markdown prose
Renames the written-usage rule into practice across the contract surface:
docs/ (architecture, research, plans, sdd), tasks/ (all 43 task files,
taskgraph validate passes), AGENTS.md, README.md, CHANGELOG.md, and the
.opencode/agents/ prompts. Every replacement is a sense-preserving
rewrite (colons, semicolons, commas, parentheses, sentence splits, varied),
never a mechanical swap; no spaced hyphens were substituted. Enum fields,
frontmatter, tables, wire data, and cited identifiers survive verbatim;
en dashes inside numeric ranges keep their meaning.

Verification: taskgraph validate (43 tasks), repo grep clean (single
exempt hit: the verification command's own search pattern in AGENTS.md),
cargo test, clippy --all-targets -D warnings, fmt --check, doc all green.
2026-11-04 20:28:00 +00:00

19 KiB
Raw Permalink Blame History

status, last_updated
status last_updated
reviewed 2026-10-29

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; see 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 argv = the assembly-supplied serve-config shell (no crate default; unset → shell_request declines, OQ-08 resolved); 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), which separates stderr the wire's command string → the configured exec wrapper argv with the command appended as final element (wrapper ++ [cmd], OQ-08; never naively split); wrapper unset → clean decline; 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 none sftp name: declines until alkfs lands, since the feature exists but the route doesn't (OQ-13 resolved: the tested decline placeholder is the shipped behavior until an alkfs-mapping ADR changes it). git service names: decline with reason, since 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_with_establisher gate shape (alktunnels 0.5.0, review-003 W2 adoption): the router's correlation lookup rides the establisher's ForwardedGate; the handler's ForwardedHandler shape hands the channel duplex to the pump per fabric arrival the door opens a forwarded-tcpip channel toward the vanilla client (a vanilla client never initiates this type, per sshd parity; an inbound open is the unvanilla class, declined ConnectFailed); echo args = the original tcpip_forward request's wire string verbatim + the actually-bound port (the sshd c->path + get_local_port parity; a port = 0 mint echoes the allocated number); originator = TunnelForwardedParams.peer; a stale/missing correlation token fails the open typed at the call plane (channel:open_failed, unknown_resource) before any channel is born (the gate's miss class); the narrow gate-pass→handler-run residual race and the cap-admission declines still teardown-at-birth (never a half-open channel); the accept is the crate's per-session channel cap (the acceptor vocabulary)
direct-streamlocal unix-socket forward tunnel SubstrateAddr::Path treatment compiles wherever tunnel compiles; serving is runtime-Unix-gated: on Unix it serves, on other platforms it is a clean open-decline (OQ-11 resolved: universal compile + runtime decline, no per-platform feature fork)
x11 X forwarding none (never) none always declines; not feature-gated (ADR-003 §5)
auth-agent agent forwarding none (never) none always declines; the anti-pattern of the alkvault custody posture (ADR-003 §5)
session → SOCKS5-in-channel (no vanilla use) none (retired, ADR-003 §1a) none 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 (+ both cancel forms), gated on tunnel (unfeatured declines both the listen requests and the data channels). The served flow (w3-tunnel-listen; the route-1 classes decided against alktunnels 0.4.1's recorded surfaces, the establisher shape against 0.5.0 (the review-003 W2 adoption), with POC-3 §C.4 as the template):

  • The listen dial, family-ward through the consumer surface: TunnelListener::open(channel_client, TunnelParams {resource: <the wire bind endpoint>, substrate: Tcp/Unix}). The resource is built from the wire bind endpoint: the address string rides verbatim (the OpenSSH client normalizes the no-address case to "localhost" before it sends (channel_rfwd_bind_host parity), so the door passes through), with the tcp port appended (host:port; 0 stays 0, because the OS-picking is the fabric's bind step, whose bound report feeds the out-param); the unix path rides alone; whose bind policy governs (wildcard vs loopback, the sshd GatewayPorts posture) is the fabric's establisher/bind closure, assembly policy at the family's provider. The dial is bounded (LISTEN_DIAL_TIMEOUT = 10 s, matching alkcall's own establisher deadline) and awaited inline in the hook by wire necessity (the global request's answer + the &mut u32 out-param are the hook's own; the fabric leg's answer doesn't ride this session's routing, so the stall is bounded, not deadlock-class); this is recorded as the flow's one inline serving step.
  • The reply contract: the minted listen token (alkcall ADR-049 amendment 3 establisher field) is REQUIRED: a producer that minted no token (or no bound report) gets a declined listen with its fabric channel reaped immediately (a listen the door cannot correlate arrivals onto never answers success). The bound port writes the &mut u32 out-param (port = 0 → the actually-bound number; sshd's allocated_listen_port reply parity; the client.md lifecycle primitive).
  • The door-side arrival surface: the fabric's bind-first accept dispatcher (make_bindfirst_accept_handler) dials the tunnel/forwarded op toward the door per arrival (the ForwardedOpener closure); the door serves it through the alkcall openable registered into the shared per-connection registry, the serve-config's TunnelListenSeam (the w3-seam-config second course-correction: the assembly's Arc<ChannelClient> alone carries no inbound-op serving loop; the seam fronts the ServingConfig.registry the assembly wired at the leg's from_connection_with_serving). The registration runs at SshAdapter::new (a failure is a failed construction, AdapterError::ListenRegistration); alkcall's own gates carry the per-identity cap + the tunnel:forwarded scope per arrival, then the crate's arrival pump takes over.
  • The arrival pump: per the forwarded-tcpip row above; unix substrate arrivals (a minted streamlocal_forward route) open forwarded-streamlocal@openssh.com with the path echoed (russh's server Handle carries the channel-open at this pin; the OQ-11 runtime-Unix gate rides the unix listen row the same way it rides the channel row: off-Unix decline, universal compile).
  • The cancel forms + bounded bookkeeping: the per-session listener map (packet-loop-only, like the channels store) matches cancels by (the wire endpoint as the request carried it, the bound port; the sshd c->path == host && listening_port == port parity; the unix forms by path alone) 1:1 into the family listener's teardown (TunnelListener::close/Drop; the route leaves the shared router before the teardown races), false for an unknown pair. The listen count is a bounded resource: a dedicated counter against the same Limits::max_concurrent_channels ceiling (the listener/channels mix stays unconstrained; both stay bounded, so no unbounded listener map). A session's teardown removes its tokens from the shared router (a fabric arrival after the session is gone is the correlation-miss class).

Request-level rows on a session channel

SSH request Family mapping
pty_request NegotiateRequest.tty = TerminalParamsWire{…} (POC-3 §D.1: live stty size honored); ttymodes → the reserved modes JSON (OQ-44); a stored pty is consumed by the later shell_request/exec_request on the channel (ssh -t host "cmd"). A pre-serve window_change_request resizes the stored params (the final size wins)
shell_request NegotiateRequest.cmd = the assembly-supplied serve-config shell argv verbatim (no crate default; unset → the serving-path decline, failure then close; never a fallback, OQ-08 resolved; never wire-supplied). A shell request without a preceding pty serves pipe-mode (tty = None), vanilla sshd's posture; ssh -T is legitimate vanilla
exec_request parse → git dispatch (git) or alktty pipe cmd = exec wrapper argv with the command string appended (wrapper ++ [cmd], OQ-08; vanilla quoting semantics, never naively split; wrapper unset → the serving-path decline, failure then close; never a fallback); unknown → channel_failure, no resource alloc
subsystem_request decline (per the rows table; honest, with reason)
env_request declined unconditionally (OQ-09 resolved; no allowlist ships); wire env is not family config
window_change_request resize control through the serving bridge (POC-3 §D.1: live resize proven); fire-and-forget, no SSH reply, so a full/closed control surface drops the event
signal signal control (alktty names verbatim, no door-side filter; PTY mode reaches the process group, pipe mode is pid-only; backend semantics and a fidelity note, OQ-10); fire-and-forget, no decline shape
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). Derivation contract rules 1, 3–6 below
exit_signal negative exit-chunk codes → exit-signal(name) (derivation contract rule 2); −1 → exit-status(255), never a signal claim (rule 3). Both the pipe and the PTY path report signal deaths as −N (alktty 0.8.2 reaps the PTY child directly; the former portable-pty flattening constraint is retired; rule 5)

Exit chunk → SSH exit reporting (the derivation contract)

The alktty exit chunk ({"type":"exit","code":N}, exit-chunk-is-last; alktty ADR-004, alknet ADR-055 lineage) is the sole fidelity input; the chunk is not widened (any widening needs its own alktty ADR; the convention is consumed as-is; OQ-10 resolved). Rules, normative for the exit_status/exit_signal rows above:

  1. code ≥ 0 → exit-status(N).
  2. Negative −N, N ≥ 2 (the Unix pipe path's signal encoding) → exit-signal(name) via a Unix signal-number→name table (server-side work; signal semantics belong to the serving platform, where the backend reaps). core_dumped is never asserted: alktty discards ExitStatus::core_dumped(), so nothing better is knowable from the chunk, and SSH's exit-signal bool stays false. (POC-3 §D.2 observed the full derivation live: signal TERM → −15 → exit-signal("TERM").)
  3. −1 → exit-status(255) (OpenSSH's conventional failure value). alktty's could-not-determine sentinel collides with the SIGHUP encoding on the Unix pipe path, and this rule accepts the fidelity cost symmetrically: neither a fabricated signal for a wait failure (a signal that never happened) nor a real one: a genuine SIGHUP death on the pipe path also reports 255, not exit-signal("HUP"). If the wait-failure vs SIGHUP distinction ever becomes load-bearing, alktty's additive exit_error control type is the extension seam (recorded in alktty's own ADR); alkssh derives from whatever that carries, with no contract change here.
  4. Non-Unix: no signal termination exists; −1 is the only negative that can appear → rule 3.
  5. The PTY path (constraint retired, alktty 0.8.2): portable-pty 0.9's ExitStatus flattens a signal-terminated Unix child to a plain code 1, so PTY-row signal deaths once arrived as exit-status(1). alktty 0.8.2 retires the constraint (its Unix PTY waiter skips portable-pty's conversion and reaps the direct child via waitpid, decoding signal deaths as −N); the PTY path now reports signal termination exactly like the pipe path, and the derivation here needed no contract change: the negative-code convention is consumed verbatim (OQ-10's upstream constraint closed). Pipe-mode pid-only vs PTY process-group scope (the signal row note) is backend semantics, not a mapping detail.
  6. Unmapped negatives → rule 3. The convention's producers are −1 and −signum only; a negative value with no entry in the signal-name table (defensive case: the mapping never invents a name) reports exit-status(255).

The structural constraint: spawn-and-return

russh (this pin: ADR-002) runs server handler callbacks inline inside the session's packet loop, which means 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, as 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) nothing everything
git git-parseable exec pty/shell, plain exec, all other channel types, all session requests
session pty/shell/pipe-exec (requires the configured shell/exec wrapper, OQ-08; an unset field declines its row though the feature is on) git-parseable exec (the git parse is its gate), other channel types
tunnel direct/forwarded-tcpip, streamlocal (unix), -R listens + arrivals (the listen forms additionally require the configured TunnelListenSeam; an egress-only door declines just the -R forms) session channels (auto-reject), all others
any build + sftp slot unchanged from the underlying build 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

None open or deferred; see open-questions.md (all of OQ-01…13 resolved). Doc-local contract anchors: OQ-02 (session dispatch, ADR-006 as amended, §above), OQ-08 (the shell argv / exec wrapper serve config, the shell_request/exec_request rows and the session→pty+shell channel row), OQ-09 (the env_request row), OQ-10 (§the derivation contract, now formalized; the former upstream PTY constraint is retired as of alktty 0.8.2, rule 5), OQ-11 (direct-streamlocal row + the listen-flow gate), OQ-13 (the sftp rows; the decline placeholder is the shipped behavior).

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)