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.
227 lines
11 KiB
Markdown
227 lines
11 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to this crate are documented here. The format is
|
||
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
||
this crate adheres to [Semantic Versioning](https://semver.org/).
|
||
|
||
## [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). |