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.
11 KiB
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::decryptreturnsZeroizing<String>(alkvault review 001, consumer input from alkssh; resolution paths 1+2). The adapter seam's ownZeroizingwrap indecrypt_and_decodeis 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, androtatenever 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
(
-Rleg's door-side arrival half) now registersregister_tunnel_forwarded_openable_with_establisherwith aForwardedGatecapturing the door'sListenRouter: a stale/missinglistentoken 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
Negativeconsequence amended, OQ-07 and OQ-10 amended + the two upstream watch items closed,mapping.md'sforwarded-tcpiprow (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'sdetails.reasonback into theTunnelEstablishErrortaxonomy (the real opener's table) instead of swallowing every call-plane refusal intoHandlerError; the typedunknown_resourcesurvives 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 pinthe_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 inpoc-1..3+reverse-runner.md; POC source never merged). - Phase 1 architecture under
docs/architecture/: the spec docs and ADRs 001–006 (Accepted): thealk/sshALPN 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; nounwrapoutside tests. - Vault-backed key custody in code: the
KeySource/HostKeySourceseams and the ADR-005 implementations (VaultKeySource,VaultHostKeySource): decrypt-at-use / decrypt-at-construction, theZeroizingseam, onedecode_secret_keyconversion. - The
HostKeyPolicyseam (OQ-05, policy-blind) with the fail-closed defaultRejectAll; 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_failurethen 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 (
tracingevents 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;1is honestly reported for pushng. - 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-streamlocalopens ride the assembly'sTunnelSessionover the family fabric; fabric failure is a cleanConnectFailedreject. - Tunnel leg 2, the
-Rlisten flow:tcpip_forward/streamlocal_forwarddial the fabric, arrivals correlate through the token router (miss → teardown-at-birth), the data leg opensforwarded-tcpip/forwarded-streamlocalback 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;SshConnectionthe 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 thesftpdecline row (always declines, every build). - The
-Rleg:reverse_listen/reverse_listen_unix+ theAcceptorRegistry(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
ListenRoutertoken-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 realsshdagainst the client direction (exec, direct-tcpip dial,-Rlisten + 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
sftpis a feature of decline: enabling it adds no capability; thesftpsubsystem request still declines in every build until the alkfs mapping lands (OQ-13; the decline stays contract territory).- The
envrequest declines unconditionally (OQ-09): session cwd/env are assembly-supplied, never wire-derived. - The russh pin (0.65,
aws-lc-rs,des/dsanever enabled) is a wire-relevant one-way door: bumps are deliberate, tested, and recorded (ADR-002).