--- status: draft last_updated: 2026-09-21 --- # alkgit-ssh: SSH Front Door ## What it is The ssh adapter: serves the git command surface over ssh. Unlike http, ssh is session-shaped, so it maps directly onto the transport duplex session (ADR-002). **Client compatibility (v1 decision):** stock git clients (`git clone ssh://git@host/repo`) speak the SSH wire protocol — an exec request over an SSH connection. alkcall channels are *not* that protocol; they are alkgit's internal session layer. v1 therefore terminates real SSH wire protocol (russh is the candidate); the vision's "not a general sshd" non-goal is untouched — only the git command surface is served. Concretely: - `alkgitd` terminates SSH (russh under the `ssh` door) and hands the adapter the post-auth exec request + stream. - alkcall channels remain the *internal* substrate identity/ACL flows through (alk-stack.md §"Interfaces in alkcall terms"); the adapter's dispatch output is the same duplex session either way. - A pure-alkcall-channels ssh door (no wire SSH) is a downstream-embedding variant only — OQ-03 territory, not v1. - Manifest note: the pre-phase-1 workspace skeleton pins only `alkcall` in `crates/alkgit-ssh/Cargo.toml`; the russh dependency (and where it sits — alkgitd vs alkgit-ssh) is set when this spec implements. ## What we serve Exactly the git command surface, not a general sshd (vision non-goals): | Command | Serves | |---|---| | `git-upload-pack ''` | fetch/clone (duplex V2 session) | | `git-receive-pack ''` | push (duplex receive-pack; OQ-04) | | `git-upload-archive` | **not served** (declined explicitly — `docs/research/git-protocol.md` §security notes; honest refusal, not silence) | ## Session flow 1. **Command dispatch**: parse the exec request string (`git-upload-pack` / `git-receive-pack` + quoted repo argument). Reject other commands with a clear error on stderr (no shell interpretation — the command string is parsed, never executed). 2. **Repo resolution + ACL** (ADR-007/008): the quoted repo argument is a registry ID; resolve, authorize (read for upload-pack, write for receive-pack — push always authenticated), all before any protocol byte. 3. **V2 negotiation**: `GIT_PROTOCOL=version=2` arrives as an ssh env line (`git-protocol.md` §framing). v1 policy: expect/require V2 per ADR-003 (V0/V1 declined with a clear error). 4. **Handoff**: the post-auth stream (from the russh-terminated session) is the duplex substrate; transport runs the duplex session (advertise-once → command loop, sideband packs). The adapter's job ends here — no protocol logic lives in this crate. ## Identity - With russh terminating SSH (see the v1 decision), peer identity comes from the ssh authentication the terminator performs (public key, typically). How that identity becomes an alkgit/alkcall identity — and where authorized keys live (vault involvement) — is **OQ-08**, shared with http; one auth session settles both doors. ## Relationship to russh russh terminates the SSH wire protocol for stock git clients (see the v1 decision above). The dispatch + ACL + handoff sequence (§Session flow) is door-agnostic: alkcall-channel and russh inputs converge on the same transport duplex session. Whether alkgit-ssh exports a russh-flavored adapter separately from the alkcall one is an OQ-03 (embedding surface) question — v1 ships the path alkgitd needs (russh termination → this adapter's dispatch). ## Limits The duplex session carries `Limits` (ADR-009). Channel-level caps (alkcall ADR-041 per-identity channel cap) still apply above us; git sessions consume one channel each (alk-stack.md §"Interfaces in alkcall terms"). ## Design Decisions | ADR | Decision | Summary | |---|---|---| | [001](decisions/001-crate-decomposition.md) | Crate decomposition | ssh is a separate replaceable adapter | | [002](decisions/002-front-door-blind-core.md) | Session boundary | duplex session = native ssh shape | | [003](decisions/003-protocol-v2-first.md) | V2-first | V0/V1 declined via env-line check | | [007](decisions/007-acl-before-advertisement.md) | ACL first | before advertisement, push always authed | | [008](decisions/008-registry-resolved-repo-identity.md) | Repo identity | exec argument = registry ID | ## Open Questions - **OQ-08**: ssh identity mechanics (open). - **OQ-03**: russh-flavored embedder adapter (deferred(scope)). ## References - `docs/research/poc-1-findings.md` (duplex session shape, git:// in-band framing analog — ssh exec is the same resolve-before-emit order) - `docs/research/alk-stack.md` §"Interfaces in alkcall terms"