Files
alkssh/Cargo.toml
T
glm-5.3-flash 3d0253d981 feat(tunnel): land the -R listen flow + the forwarded-tcpip data leg (w3-tunnel-listen)
The tunnel branch's other half is served for real against published
alktunnels 0.4.1 / alkcall 0.10.1, with POC-3 §C.4 as the template:

- tcpip_forward/streamlocal_forward (+ both cancel forms) map the
  vanilla listen request family-ward through TunnelListener::open on
  the held ChannelClient (bounded inline dial — the global request's
  reply + the &mut u32 bound-port out-param are the hook's); the
  wire bind endpoint rides as the resource string verbatim (OpenSSH
  client-side normalization parity). A reply without the minted
  listen token/bound report declines and reaps its fabric listener.
- the door serves the fabric's inbound tunnel/forwarded opens: the
  serve config gains TunnelListenSeam (the seam-config second
  course-correction — the shared ServingConfig registry the assembly
  wired at from_connection_with_serving); alkssh registers the
  forwarded openable (ChannelCore over a clone of the held client's
  manager) at SshAdapter::new — a failed registration is a failed
  construction (AdapterError::ListenRegistration).
- the forwarded-tcpip data leg: token correlation through the shared
  ListenRouter → the in-crate per-session channel cap at the accept
  surface (the acceptor vocabulary) → Handle::
  channel_open_forwarded_tcpip streaming the listen request's own
  args + the actually-bound port (sshd parity) with
  TunnelForwardedParams.peer as the originator → pump_bidi(ssh,
  fabric). Correlation-miss → teardown-at-birth, never a half-open
  SSH channel. Unix arrivals ride forwarded-streamlocal@openssh.com.
- bounded bookkeeping: the per-session listener map packet-loop-only
  with a dedicated max_concurrent_channels-ceiling counter; the
  route map bounded by construction (mints at accepted listens,
  removed at cancels + session teardown — the handler's Drop).
- the harness grew the client-side accept hook
  (server_channel_open_forwarded_tcpip/_streamlocal,
  acceptor-driven) + a hand-rolled tracing subscriber; 18 contract
  rows + both real-tool smokes (ssh -L, ssh -R + curl) green in
  tests/tunnel_mapping.rs; the wave-2 stub listen rows migrated per
  build (unfeatured / seam-unconfigured).

Verified: cargo test across default/git/session/sftp/tunnel and all
combos + --all-features; clippy -D warnings (default, all-features,
combos); cargo fmt --check; cargo doc --no-deps clean;
taskgraph validate (26 tasks).
2026-10-29 04:24:50 +00:00

107 lines
5.3 KiB
TOML

# Single-crate protocol/door crate (the alktty template). No workspace:
# the composition story is deferred — alkssh's substance is the mappings
# (family ADR-003).
[workspace]
members = ["."]
[package]
name = "alkssh"
version = "0.1.0"
edition = "2024"
rust-version = "1.89"
license = "MIT OR Apache-2.0"
description = "SSH adapter for the alk family: russh-wrapped vanilla-ssh termination bridged to/from alkcall channels — session/pty, tunnels, git exec — with every capability mapping a fail-closed cargo feature (no features: no capabilities)"
repository = "https://git.alk.dev/alkdev/alkssh"
keywords = ["ssh", "russh", "protocol", "alkcall", "network"]
categories = ["network-programming", "asynchronous"]
# The package policy (the sibling crates' convention): the published crate
# ships the architecture docs (the authoritative contract) but not the
# process surface — reviews, research, plans, task files, the working
# agents' config, AGENTS.md.
exclude = [".opencode/", "AGENTS.md", "docs/reviews/", "docs/plans/", "docs/research/", "docs/sdd_process.md", "tasks/"]
[lib]
name = "alkssh"
[features]
# ADR-003: flat and independent — no feature implies another; `default = []`
# by design (a default build has no capabilities; every unfeatured open
# declines fail-closed). The wave-2 decline core is the first `cfg`
# consumer; the wave-3 seam-config handles are the second (each optional
# sibling dep is gated feature-to-dep so a git-build never compiles
# alktty and vice versa — the wave-1 manifest rule: per-feature sibling
# deps land with their first consuming wave, which for alktty/alkgit is
# the wave-3 config fields and for alktunnels `w3-tunnel-direct`, the
# route-1 bridge).
default = []
session = ["dep:alktty", "dep:serde_json", "dep:bytes"]
git = ["dep:alkgit"]
tunnel = ["dep:alktunnels"]
sftp = []
[dependencies]
# async-trait (the alkcall precedent) makes the injected handles
# dyn-compatible (`Arc<dyn ...>`) — the HostKeyPolicy dial handle needs
# that, and native AFIT traits are not object-safe at this toolchain.
async-trait = "0.1"
# ADR-002: aws-lc-rs only; default features off — no flate2, no rsa
# (ed25519-only; the tree stays RC-free except the ssh-key pin riding the
# russh keys path). Never enable russh's `des`/`dsa` features.
russh = { version = "0.65", default-features = false, features = ["aws-lc-rs"] }
alkcall = "0.10"
alkvault = "0.1"
# Wave-3 seam-handle types (`config.rs`'s `SessionSeam`/`GitSeam`).
# Pins verified against crates.io (2026-10-28): alktty 0.8.1, alkgit
# 0.1.0 — both MIT OR Apache-2.0, both rust_version 1.88 ≤ our 1.89,
# both over alkcall ^0.10 (shared with our `alkcall = "0.10"` → 0.10.1
# resolution). alktty's `local` feature stays off — alkssh never
# touches a real backend; the `local` shapes stay alktty-side.
alktty = { version = "0.8.1", default-features = false, optional = true }
alkgit = { version = "0.1.0", default-features = false, optional = true }
# tunnel-only: the route-1 bridge's consumer surface (`TunnelSession::
# open_direct`/`take_halves`, `SubstrateAddr`/`Substrate`). Pin
# re-verified against crates.io at `w3-tunnel-direct` (2026-10-29):
# 0.4.1 is `max_stable_version`/`newest_version`, MIT OR Apache-2.0,
# rust_version 1.88 ≤ our 1.89, alkcall ^0.10.1 pinned exactly
# (single-versioned against our `alkcall = "0.10"`). The `local`
# feature stays off — the dial seam is injected (`DialFn`); the real
# dial/backends belong to the assembly, and the tests hand-roll their
# own dial/dial-fail seams (`tests/tunnel_mapping.rs`).
alktunnels = { version = "0.4.1", default-features = false, optional = true }
# session-only: the OQ-44 reserved ttymodes JSON (`TerminalParamsWire.modes`)
# and the exit-chunk / negotiation-error-frame JSON reads in `tty_map.rs`.
# Same pin alktty carries (crate resolution stays single-versioned).
serde_json = { version = "1", optional = true }
# session-only: the alktty chunk plane is `bytes`-based (`Chunk.bytes`) —
# the bridge builds sentinel/ctrl chunks with it. Same pin russh/alktty
# carry (crate resolution stays single-versioned).
bytes = { version = "1", optional = true }
tokio = { version = "1.54", features = ["full"] }
thiserror = "2.0"
tracing = "0.1"
parking_lot = "0.12"
zeroize = "1.9"
[dev-dependencies]
# The git-seam test doubles name `GitPackGen`'s `gix_hash::ObjectId`
# parameters and alkgit does not re-export them — test-graph only,
# version matched to alkgit's `^0.28` req (same crate in the lockfile).
gix-hash = { version = "0.28", features = ["sha1"] }
# The session shim's scripted stdout/stderr streams ride tokio's
# `ReceiverStream` — test-graph only (the crate proper hand-rolls no
# streams).
tokio-stream = "0.1"
# The leg-2 fabric double's establisher/params serialization
# (`w3-tunnel-listen`) — test-graph only (the door's own lib code
# stays serde-free under the `tunnel` feature; the double's
# dynamic-bind producer serializes params/reply fields).
serde_json = "1"
# The real-tool pty smoke (`w3-tty-session`): alktty's `local` backend
# composition — the dev-dep `features = ["local"]` unification applies
# to the TEST graph only (feature unification folds `local` into the
# optional alktty dep in test builds; the published lib never enables
# it and `src/` never names a local shape). Same 0.8.1 pin.
alktty = { version = "0.8.1", default-features = false, features = ["local"] }
[profile.release]
lto = "thin"