Files
alkssh/CHANGELOG.md
glm-5.3-flash f4b5f896b4 docs: sweep em dashes from all markdown prose
Renames the written-usage rule into practice across the contract surface:
docs/ (architecture, research, plans, sdd), tasks/ (all 43 task files,
taskgraph validate passes), AGENTS.md, README.md, CHANGELOG.md, and the
.opencode/agents/ prompts. Every replacement is a sense-preserving
rewrite (colons, semicolons, commas, parentheses, sentence splits, varied),
never a mechanical swap; no spaced hyphens were substituted. Enum fields,
frontmatter, tables, wire data, and cited identifiers survive verbatim;
en dashes inside numeric ranges keep their meaning.

Verification: taskgraph validate (43 tasks), repo grep clean (single
exempt hit: the verification command's own search pattern in AGENTS.md),
cargo test, clippy --all-targets -D warnings, fmt --check, doc all green.
2026-11-04 20:28:00 +00:00

11 KiB
Raw Permalink Blame History

Changelog

All notable changes to this crate are documented here. The format is based on Keep a Changelog, and this crate adheres to Semantic Versioning.

[0.2.0] - 2026-11-04

The sibling-adoption release: the three alkssh-filed upstream reviews resolved across the family, and alkssh adopts the results. Two dependency requirement bumps (the minor bump, not a patch: dep-req changes are semver-significant for consumers) plus the review-003 W2 adoption wave. No alkssh-side wire-format change, no public API change apart from what the dep bumps ripple through.

Changed

  • alkvault 0.1 → 0.2 (breaking req): VaultServiceHandle::decrypt returns Zeroizing<String> (alkvault review 001, consumer input from alkssh; resolution paths 1+2). The adapter seam's own Zeroizing wrap in decrypt_and_decode is dropped (it would be a dead double wrap): the pre-wrap unfenced plaintext window ADR-005 decision 3 recorded is closed upstream; the vault fences the plaintext from its return onward, on every path including its UTF-8 error path, and rotate never holds a bare plaintext either. decode_key_arm's signature is unchanged (it kept riding &Zeroizing<String>; the posture now proven by the vault's return type instead of the adapter seam).
  • alktunnels 0.4.1 → 0.5.0 (breaking req, additive API): the review-003 W2 adoption: the forwarded serving surface (-R leg's door-side arrival half) now registers register_tunnel_forwarded_openable_with_establisher with a ForwardedGate capturing the door's ListenRouter: a stale/missing listen token fails the open typed at the call plane (channel:open_failed, details.reason == "unknown_resource") before any channel is born, closing the phantom-channel shape (call-plane success, then handler-side decline) ADR-008 Amendment 2 targets; a malformed arrival is likewise typed (handler_error) instead of success-then-silent-birth-teardown. The handler-side correlation re-lookup keeps the residual-race + cap-admission teardown-at-birth fallback (the base shape's posture, retained). The wire delta is empty (additive registration only); the ACL (tunnel:forwarded) still runs before the establisher (now double-pinned by the unscoped-arrival row, which uses a genuinely minted token).
  • alktty 0.8.1 → 0.8.2 (patch): the PTY exit-signal-fidelity fix (alktty review 004, consumer input from alkssh); the requirement resolves to 0.8.2 automatically; the pin text refreshed. No alkssh contract change: the negative-code convention (OQ-10's derivation contract) is consumed verbatim, so PTY-row signal deaths now report exit-signal(name) through the door exactly as pipe-mode already did; OQ-10's upstream constraint (the portable-pty flattening) is retired.

Documentation

  • ADR-005 decision 3 amended (the gap is closed upstream; the adapter's obligation shrinks to consuming the zeroized value in one pass), the Negative consequence amended, OQ-07 and OQ-10 amended + the two upstream watch items closed, mapping.md's forwarded-tcpip row (the gate shape) and derivation-contract rule 5 (the PTY constraint retired) updated, client.md's OQ-07 anchor updated.

Fixed

  • The test doubles' fidelity: tests/tunnel_mapping.rs's harness opener now maps the wire refusal's details.reason back into the TunnelEstablishError taxonomy (the real opener's table) instead of swallowing every call-plane refusal into HandlerError; the typed unknown_resource survives the client side, which is exactly what the new gate rows pin.

New rows

  • a_correlation_miss_fails_the_open_typed_before_any_channel (the gate miss: typed refusal, no phantom channel, no acceptor row), a_gate_pass_arrival_serves_the_route_unchanged (the gate-pass posture: the serving flow is the unchanged shape), and the source unit pin the_pump_residual_miss_teardown_at_births_the_fabric_half (the handler-side residual miss stays teardown-at-birth).

[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 '/<repo>' 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).