docs(release): wave 5 release readiness — crate docs, README, CHANGELOG, matrix, dry-run (w5-release-readiness)

- resolve the rustdoc warning set (9 incl. private-link class in
  client_ops.rs + redundant explicit links + a duplicated tty_map
  paragraph): cargo doc warning-free on default and --all-features
- docs honesty: the five recorded limits ride the published
  crate-root rustdoc (§Known limits: write_all no-deadline, PTY
  rule-5, port-0-only mint echo, no-reconnect policy, no-accept-all);
  crate docs state the feature graph + the fail-closed invariant
- two no_run usage doctests (server mount + dial) — the README
  examples stay compile-checked in every matrix build
- README.md lands (the door, feature graph, examples, custody
  posture, license); CHANGELOG.md carries the 0.1.0 founding entry
- feature matrix green (6 test builds, clippy x2 -D warnings, fmt,
  doc) + the fuzz corpus gate (11 invariants)
- cargo publish --dry-run --allow-dirty clean (76 files packaged +
  verified; file list checked against the package policy; manifest
  sanity recorded); the freeze inventory recorded for the gate

Verification: scripts/matrix.sh (all builds), fuzz corpus gate,
taskgraph validate
This commit is contained in:
glm-5.3-flash committed 2026-10-30 19:29:23 +00:00
1 parent d37cbc0c8a
commit ab5236ebbb
9 files changed
+634 -53

No files matched your search

+3 -3
View File
@@ -35,7 +35,7 @@
//! `ForwardedAcceptor` vocabulary is grown, not forked: the payload
//! fields are the same shapes (the open's originator fields + the
//! channel duplex), the duplex now family-typed
//! ([`TunnelChannel`](crate::TunnelChannel) — the `-L` rows' byte
//! ([`TunnelChannel`] — the `-L` rows' byte
//! duplex, one type both legs ride).
//! - **Install is refuse-never-replace** ([`AcceptorRegistry::
//! install_tcpip`]'s held-slot error): the same posture recorded
@@ -119,7 +119,7 @@
//! ## The data leg
//!
//! Per accepted open: the hook accepts the channel and hands the
//! assembly the open — the [`TunnelChannel`](crate::TunnelChannel)
//! assembly the open — the [`TunnelChannel`]
//! duplex + the open's echo/originator fields (the payload types
//! below) — on a spawned task (the spawn-and-return rule's client
//! mirror: the hook runs in the packet loop and returns fast). The
@@ -221,7 +221,7 @@ impl std::fmt::Debug for ForwardedStreamlocal {
/// packet loop): the assembly's pump/home of the open lives inside
/// it; the crate releases its handoff counter when the handoff task
/// ends — the call returning *or the task unwinding*
/// ([`HandoffCapGuard`]) — so the family shape (hand off to a queue,
/// (`HandoffCapGuard`) — so the family shape (hand off to a queue,
/// return) keeps the counter's meaning "open accepted, handoff in
/// flight".
#[async_trait]
+1 -1
View File
@@ -25,7 +25,7 @@
//! through the **acceptor registry** ([`crate::client_accept`] —
//! the `w4-reverse-listen` surface): served only under a registered
//! acceptor for that type and under the `tunnel` feature (the
//! [`with_tunnel_surface`](ClientHandler::with_tunnel_surface)
//! [`with_tunnel_surface`]'s tunnel surface
//! wiring; a build without `tunnel` has no registry — every hook
//! declines); unregistered/unfeatured opens answer
//! [`ChannelOpenFailure::AdministrativelyProhibited`] — a clean
+7 -6
View File
@@ -221,7 +221,7 @@ impl SshConnection {
/// A remote **session** — the family session plane over the runner
/// (the session rows' surface): the alktty chunk framing in both
/// directions, exactly the plane the server direction's session
/// bridge speaks ([`crate::tty_map`]), reversed. Two duplex halves
/// bridge speaks (`tty_map`), reversed. Two duplex halves
/// the hub drives; the pumps are spawned at the op's launch (§the
/// family plane in the module docs for the full chunk map). Dropping
/// the session (both halves) is wire-clean: the input pump's
@@ -269,7 +269,8 @@ impl SshConnection {
}
/// The pty-mode exec row (`ssh -t host "cmd"`): the pty request
/// carries the family [`TerminalParamsWire`](alktty::negotiation::
/// carries the family
/// [`alktty::negotiation::TerminalParamsWire`](alktty::negotiation::
/// TerminalParamsWire) first (its acceptance drained), then the
/// exec. **A refused pty refuses the whole row** — never a
/// half-capability, never a silent pty-less fall.
@@ -683,17 +684,17 @@ impl std::fmt::Debug for GitPackSession {
impl SshConnection {
/// The upstream git row: the shared exec grammar's command
/// (`git-{upload,receive}-pack '/<repo>'` — built through
/// [`crate::dispatch`]'s construction arm, the parse arm's exact
/// `crate::dispatch`'s construction arm, the parse arm's exact
/// inverse; no forked grammar), pointed at the vanilla `sshd`:
/// typed open + `exec`, the pack duplex pumped, the exit report
/// consumed.
///
/// **The pre-wire gates, riding the shared grammar's verdicts:**
/// the repo id validates against the ADR-008 grammar (the git
/// row's server-direction gate mirrored,
/// [`crate::git_map`]) → [`OpError::InvalidRepoId`](crate::OpError);
/// row's server-direction gate mirrored, `crate::git_map`) →
/// [`OpError::InvalidRepoId`](crate::OpError);
/// the constructed command is checked against the exec-length
/// budget ([`Limits::max_exec_command_len`]) →
/// budget ([`crate::Limits::max_exec_command_len`]) →
/// [`OpError::CommandTooLong`](crate::OpError) — both **before any
/// wire op**. (The grammar's other decline classes —
/// `unknown-service` / `service-only` — are structurally
+1 -1
View File
@@ -92,7 +92,7 @@ pub enum KeySourceError {
/// ADR-005 semantics: the vault decrypted *something*
/// successfully and the plaintext is not an OpenSSH
/// private-key PEM (decision 1's inner format). Distinct from
/// [`Decryption`](KeySourceError::Decryption) on purpose — the
/// [`KeySourceError::Decryption`] on purpose — the
/// custody boundary held, what was guarded is not a key. The
/// underlying parse error's message is deliberately *not*
/// carried — the machinery's text can echo plaintext-shaped
+167 -3
View File
@@ -24,6 +24,170 @@
//! lifecycle primitives — are landed through wave 4; the validation
//! suite and the release are wave 5). See also `docs/research/`,
//! `docs/plans/implementation.md`, and family ADR-003.
//!
//! # The feature graph (ADR-003 — flat, independent, fail-closed)
//!
//! Every channel-type mapping is a cargo feature; a session opening an
//! unfeatured or unimplemented channel type is declined fail-closed —
//! never a half-capability, never a silent fallback. **A default build
//! (no features) has no capabilities**: it can serve nothing but clean
//! declines. No feature implies another; the graph is flat.
//!
//! - (default, no features) — serves declines only: every open, exec,
//! listen request answers a clean, channel-scoped refusal.
//! - `session` — pty/shell/exec rows → the `RemoteSession` chunk
//! plane (alktty).
//! - `git` — the exec-only git rows (`git-{upload,receive}-pack`) →
//! alkgit; a git *subsystem* request always declines (the repository
//! can never ride a subsystem channel).
//! - `tunnel` — `direct-tcpip` / `direct-streamlocal` and the `-R`
//! legs (listen, `forwarded-tcpip` / `forwarded-streamlocal`) →
//! alktunnels.
//! - `sftp` — a reserved, currently-empty placeholder (OQ-13): the
//! `sftp` subsystem request still declines in every build until the
//! alkfs mapping lands; the feature exists so the decline stays
//! contract territory (tested in every matrix build).
//!
//! Advertisement is honest: an unfeatured channel type is never
//! advertised, only declined (`docs/architecture/mapping.md` §Decline
//! matrix — the fail-closed invariant is contract territory on both
//! sides of every feature).
//!
//! # Known limits (recorded dispositions riding the published docs)
//!
//! The records below are deliberate dispositions — documented limits
//! with their reasons, mirrored in the module docs and the
//! architecture spec, not defects awaiting a fix:
//!
//! - **The session rows' stdout `write_all` carries no deadline.**
//! Backpressure is bounded (the bounded bridging duplex, russh's
//! per-channel window, the client's own flow control) and liveness
//! is covered by the always-set
//! [`Limits::inactivity_timeout`]; a deadline here would be a
//! behavior change (a slow client may stall its stream — vanilla
//! sshd's posture included). (`tty_map.rs` module docs.)
//! - **The PTY path's exit-signal fidelity is limited by upstream**
//! (exit-currency rule 5): signal-terminated PTY sessions surface as
//! `exit-status(1)` because the backend's reap surface loses the
//! signal number; the pipe path encodes negatives faithfully. An
//! alktty/portable-pty ask — the contract adapts automatically
//! when upstream conforms.
//! - **The port-0 mint is the only bound-port echo the wire carries**
//! (russh/sshd parity): a `tcpip_forward` success reply carries the
//! allocated port only for a `port = 0` request; an explicit-port
//! request's bound port is by definition the requested one — the
//! crate surfaces it as such, never a fabricated number.
//! - **No reconnect/restore policy.** The client direction ships
//! lifecycle *primitives* (bound ports, cancellable handles, clean
//! [`SshConnection::close`]); reconnect-and-restore (rebinding
//! persisted tunnel ports) is assembly composition over them — the
//! crate never owns the policy. Drop is never the clean path at
//! this russh pin: only an explicit `close` sends the clean
//! disconnect.
//! - **No accept-all host-key policy exists in the crate.** The
//! built-in default is [`RejectAll`] (a dial with it can never
//! connect — by design); the crate is policy-blind and any
//! permissive trust policy is the assembly's to write. A
//! fail-closed policy posture: when a policy cannot decide, it must
//! decline.
//!
//! # Usage
//!
//! The crate is a door, not a deployment: an assembly embeds it,
//! enables the features it wants, and injects the seams (identity,
//! key sources, the per-feature family handles). The examples are
//! compile-checked (`no_run`) doctests — honest sketches, not
//! executable code.
//!
//! A server mount (the default build — a decline-only door; the
//! assembly's real sources are the vault-backed [`VaultKeySource`] /
//! [`VaultHostKeySource`] shapes):
//!
//! ```rust,no_run
//! use std::sync::Arc;
//!
//! use async_trait::async_trait;
//!
//! struct Identity;
//!
//! impl alkcall::core::auth::IdentityProvider for Identity {
//! fn resolve_from_fingerprint(
//! &self,
//! _: &str,
//! ) -> Option<alkcall::core::auth::Identity> {
//! None // the assembly's registry decides
//! }
//!
//! fn resolve_from_token(
//! &self,
//! _: &alkcall::core::auth::AuthToken,
//! ) -> Option<alkcall::core::auth::Identity> {
//! None
//! }
//! }
//!
//! struct HostKeys;
//!
//! #[async_trait]
//! impl alkssh::HostKeySource for HostKeys {
//! async fn load(
//! &self,
//! ) -> Result<Vec<russh::keys::PrivateKey>, alkssh::KeySourceError> {
//! Err(alkssh::KeySourceError::Absent) // sketch; vault-backed in prod
//! }
//! }
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # tokio::runtime::Runtime::new()?.block_on(async {
//! let serve = alkssh::ServeConfig::new(Arc::new(Identity), Arc::new(HostKeys));
//! let adapter = alkssh::SshAdapter::new(serve).await?;
//!
//! // per accepted TCP connection from the assembly's listener:
//! // adapter.handle(stream).await?;
//! # Ok::<(), alkssh::AdapterError>(())
//! # })?;
//! # Ok(())
//! # }
//! ```
//!
//! A dial (the client direction; the built-in [`RejectAll`]
//! host-key policy connects to nothing — by design, the assembly's
//! policy replaces it):
//!
//! ```rust,no_run
//! use std::sync::Arc;
//!
//! use async_trait::async_trait;
//!
//! struct DialKey;
//!
//! #[async_trait]
//! impl alkssh::KeySource for DialKey {
//! async fn load(&self) -> Result<russh::keys::PrivateKey, alkssh::KeySourceError> {
//! Err(alkssh::KeySourceError::Absent) // sketch; vault-backed in prod
//! }
//! }
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # tokio::runtime::Runtime::new()?.block_on(async {
//! let conn = alkssh::dial(alkssh::DialConfig::new(
//! "hub.example:22",
//! "runner",
//! Arc::new(DialKey),
//! Arc::new(alkssh::RejectAll),
//! ))
//! .await?;
//!
//! # #[cfg(feature = "session")]
//! # {
//! # let _ = conn.exec("uptime"); // the op rows (feature-gated)
//! # }
//! conn.close().await; // clean disconnect — drop is never the clean path
//! # Ok::<(), alkssh::DialError>(())
//! # })?;
//! # Ok(())
//! # }
//! ```
mod adapter;
mod channel_cap;
@@ -71,11 +235,11 @@ pub use client_accept::{
#[doc(hidden)]
pub use client_handler::ClientHandler;
/// The client op rows' surfaces (`w4-op-rows`):
/// [`RemoteSession`](client_ops::RemoteSession) — the exec/pty rows'
/// [`RemoteSession`] — the exec/pty rows'
/// family chunk plane (under `session`),
/// [`TunnelChannel`](client_ops::TunnelChannel) — the direct
/// [`TunnelChannel`] — the direct
/// rows' byte duplex (under `tunnel`),
/// [`GitPackSession`](client_ops::GitPackSession) — the upstream git
/// [`GitPackSession`] — the upstream git
/// row's pack duplex + consumed exit (under `git`). The op methods
/// live on [`SshConnection`] (the op-row surface). Not a re-export
/// here: the `sftp` row is [`SshConnection::sftp`] itself — the
-3
View File
@@ -152,9 +152,6 @@
//! The report is sent **before** close, from the spawned serving
//! task (ADR-006 decision 4, via the cloned `Session::handle()`).
//!
//! The report is sent **before** close, from the spawned serving
//! task (ADR-006 decision 4, via the cloned `Session::handle()`).
//!
//! ## Cancellation (bounded resources + alktty's kill-on-drop)
//!
//! The serving task watches two signals: the **per-channel cancel**