# Changelog All notable changes to this crate are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this crate adheres to [Semantic Versioning](https://semver.org/). ## [0.1.0] - 2026-10-30 The founding entry — the crate's first release. `alkssh` is the alk family's SSH adapter (family ADR-003): a thin wrapper around russh 0.65 terminating the vanilla-ssh wire and bridging to/from alkcall channels — the two-direction door for vanilla clients. The wire is russh's; the crate's substance is the channel-type mappings (session/pty → alktty, direct/forwarded-tcpip → alktunnels, git exec → alkgit), each behind its cargo feature in a flat, independent graph (ADR-003): **a default build has no capabilities and serves nothing but clean declines** — every unfeatured or unimplemented request is declined fail-closed, never half-served or silently fallen back, and never advertised. Identity is the family's: a verified key's fingerprint → alkcall `IdentityProvider`; keys the crate holds come from alkvault-backed sources, encrypted at rest, decrypted at use, never env vars (ADR-005). The crate is a door, not a backend — no binary, no deployment policy; a downstream app embeds it, enables features, and brings identity/ACL configuration. ### Added **Phase 0/1 — research and architecture** (the contract the implementation lands): - SDD process, phase 0 research set (`docs/research/`), and the three passed POCs (auth bridge, vault keys, mapping bake-off; findings in `poc-1..3` + `reverse-runner.md`; POC source never merged). - Phase 1 architecture under `docs/architecture/`: the spec docs and ADRs 001–006 (Accepted) — the `alk/ssh` ALPN pin; the russh 0.65.0 pin (aws-lc-rs, no compression, rsa off); the capability feature graph; auth dispositions; vault-key custody; the mapping/bridge/ dispatch decisions. **Wave 1 — foundations:** - The manifest: the russh 0.65 pin (`aws-lc-rs`, default features off), the flat feature graph (`session` / `git` / `tunnel` / `sftp`, `default = []`). - The fail-closed error taxonomy (`thiserror`): `AdapterError`, `DialError`, `KeySourceError`, `OpError`, `OpenDeclineReason` — declines are dispositions, never panics; no `unwrap` outside tests. - Vault-backed key custody in code: the `KeySource` / `HostKeySource` seams and the ADR-005 implementations (`VaultKeySource`, `VaultHostKeySource`) — decrypt-at-use / decrypt-at-construction, the `Zeroizing` seam, one `decode_secret_key` conversion. - The `HostKeyPolicy` seam (OQ-05, policy-blind) with the fail-closed default `RejectAll` — no accept-all built-in exists in the crate. - The contract-invariant fingerprint computation (the crate's single fingerprint path, the `SHA256:` format both the auth bridge and the host-key policy consume). - The config surface: `ServeConfig`, `DialConfig`, `Limits` (the bounded-resources budget model — wall clock always set, channel caps, keepalive posture) — unset fields decline at request time (OQ-08), construction never validates. **Wave 2 — the server stack:** - `SshAdapter` — the mount point: `SshAdapter::new(serve)` builds the russh server from the config (fail-closed on key-source failure); `adapter.handle(stream)` serves one connection. - The auth bridge: russh auth hooks → ADR-004 dispositions — verified-key fingerprint → `IdentityProvider::resolve_from_fingerprint`, fail-closed on every path; bounded auth attempts. - Parse-only dispatch + the fail-closed decline core: every unroutable or unfeatured request answers a clean, channel-scoped decline; the spawned serving-path shapes (`channel_failure` then close). - Spawn-and-return plumbing (ADR-006 decision 4): serving paths run spawned (russh handlers return fast at this pin), with the exit-status-before-close mechanics. - Bounded resources at the handler surface: per-connection channel caps (refuse-never-queue) and listener caps. - The in-process loopback harness (the contract-test vehicle). **Wave 3 — the server mappings (the spine):** - Per-feature seam handles (`SessionSeam`, `GitSeam`, `TunnelListenSeam`): unset seam → decline even with the feature on. - Decline-path tracing (`tracing` events on every decline class). - The git mapping (exec-only, ADR-006): the full exec grammar — `git-{upload,receive}-pack '/'` parsed and dispatched to alkgit; a subsystem request can never carry the repository and declines; `1` is honestly reported for push `ng`. - The session mapping: pty/shell/exec through the alktty seam over a translation bridge (chunk-framed both directions), OQ-44's reserved ttymodes JSON, the exit-chunk → SSH exit derivation (rules 1–6), control rows (window-change, signal) on a bounded per-channel surface. - Tunnel leg 1: the route-1 bridge — `direct-tcpip` / `direct-streamlocal` opens ride the assembly's `TunnelSession` over the family fabric; fabric failure is a clean `ConnectFailed` reject. - Tunnel leg 2: the `-R` listen flow — `tcpip_forward` / `streamlocal_forward` dial the fabric, arrivals correlate through the token router (miss → teardown-at-birth), the data leg opens `forwarded-tcpip`/`forwarded-streamlocal` back toward the client, port-0 mints echo the actually-bound port. **Wave 4 — the client direction:** - The dial surface: `dial(DialConfig)` — key load (decrypt-at-dial), bounded russh client config, the host-key policy call, exactly one publickey attempt; `SshConnection` the handle. - The op rows: `exec` / `pty_exec` / `shell` (the alktty chunk plane), `tunnel_direct` / `tunnel_direct_unix` (byte duplex), `git_service` (pack duplex + consumed exit) — plus the `sftp` decline row (always declines, every build). - The `-R` leg: `reverse_listen` / `reverse_listen_unix` + the `AcceptorRegistry` (refuse-never-replace) and the acceptor traits — the hub asks the vanilla server to listen and serves the accepted channels. - Lifecycle primitives (OQ-12): explicitly cancellable listener handles, clean `close()` (`Disconnect::ByApplication`) — drop is never the clean path at this pin; no reconnect/restore policy (the crate ships primitives, the assembly composes). - Hardening riders: the git row's status-before-close by construction, the channel-not-held guard wire rows, tty control-row coverage, the `ListenRouter` token-collision pre-check. **Wave 5 — validation and release:** - The decline-matrix contract suite: every mapping-decline row × every build it can appear in × both directions (the matrix runner `scripts/matrix.sh` — one command over all feature builds). - Real-tool e2e both directions: OpenSSH (pty/shell/exec, `-L`, `-R`, `-D` + curl), git clone and push through the door, and a real `sshd` against the client direction (exec, direct-tcpip dial, `-R` listen + acceptor) — check-gated, skipped rows carry reasons. - The `fuzz/` sidecar: the ingress surfaces (exec parse grammar, fingerprint format, key decode, `TerminalParamsWire`) with the committed-seed corpus gate (`cargo test --manifest-path fuzz/shared/Cargo.toml`). - Release readiness: warning-free crate docs with the known-limits records riding the published rustdoc, this README, and the publish dry-run posture. ### Notes - `sftp` is a feature of decline: enabling it adds no capability — the `sftp` subsystem request still declines in every build until the alkfs mapping lands (OQ-13; the decline stays contract territory). - The `env` request declines unconditionally (OQ-09): session cwd/env are assembly-supplied, never wire-derived. - The russh pin (0.65, `aws-lc-rs`, `des`/`dsa` never enabled) is a wire-relevant one-way door: bumps are deliberate, tested, and recorded (ADR-002).