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:
1 parent
d37cbc0c8a
commit
ab5236ebbb
9 files changed
+634
-53
No files matched your search
@@ -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]
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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**
|
||||
|
||||
Reference in new issue
Block a user