ab5236ebbb03ce0d3ba3922df86843fb85b83cb8
71
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ab5236ebbb |
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 |
||
|
|
d37cbc0c8a |
test(fuzz): the four-target fuzz sidecar (w5-fuzz-harness)
The family standing rule (anything worth fuzzing gets fuzzed) on the
crate's ingress surfaces, per the alkgit convention:
- fuzz_targets/{exec_parse,fingerprint,key_decode,terminal_params} —
thin fuzz_target! wrappers over four ingress surfaces:
- exec_parse: the production parse path (from_utf8-fallback ingress
shape), differentially paired against a from-spec grammar oracle;
typed routing classes + determinism.
- fingerprint: blob parse + the single fingerprint path; the
SHA256:/unpadded-base64 contract shape via a from-spec
base64 round-trip oracle; the committed ssh-keygen -lf vectors
replayed inside the gate.
- key_decode: the production decode arm (the #[doc(hidden)]
decode_key_arm exposure, routed through itself); typed-class +
bounded-fixed-message invariants; passphrase-shape robustness
drive on the underlying decode.
- terminal_params: the pty_request conversion via the #[doc(hidden)]
terminal_params exposure; exact clamp, verbatim term, the
reserved-modes {} JSON shape, serialized-shape determinism.
- shared/ — the invariant logic as a stable-clean library; the
corpus gate (cargo test --manifest-path fuzz/shared/Cargo.toml)
replays every committed seed through the batteries and joins
AGENTS.md's verification list.
- corpus/<target>/ committed seeds + the deterministic
gen_fuzz_seeds.py (no-op-diff verified).
- run-detached.sh + nightly pin; the sidecar excluded from the root
workspace (workspace + package excludes; publish dry-run clean).
Exposure shape decided + recorded per target (README + lib.rs doc
comments): fingerprint needs none (fully public ingress); the rest
are #[doc(hidden)] re-exports of the production surfaces, never
forks. Detached campaigns ran per target (600 s default budget, fork
mode): all four clean; the two findings of the first round are
recorded in the task file (the harness oracle's double-/-strip bug;
the PPK echo fixed in the preceding commit).
Verification: corpus gate green (11 tests); scripts/matrix.sh green;
taskgraph validate clean.
|
||
|
|
a5fb03e7c6 |
fix(key-source): decode failures carry a crate-owned message
The fuzz campaign (w5-fuzz-harness) found russh's ssh-key PPK parse path interpolating the raw header line — the caller's plaintext bytes — into its Display; passing that message through KeySourceError::Decode's payload could echo decrypted-record material into a stored/logged error string (ADR-005 decision 7's textual leak). decode_key_arm (the production decrypt-and-decode decode arm, exposed #[doc(hidden)] for the sidecar) now maps every decode failure to a crate-owned fixed message naming the refusal — the machinery's text never rides the typed payload. The upstream no-echo ask is recorded for russh/ssh-key. Verification: scripts/matrix.sh green; the fuzz sidecar's key_decode campaign re-ran clean (606 s, fork mode, zero crashes). |
||
|
|
69d888a231 |
feat(e2e): client-direction real-sshd e2e (w5-e2e-client)
The wave-5 client-direction real-tool e2e: a dedicated `tests/e2e_client.rs` binary dialing a per-row spawned real OpenSSH server with the shipped crate — exec (exit-status parity through real reaping), the direct-tcpip dial, the `-R` leg (port-0 mint + accepted arrival + data leg + cancel tearing the real listen down + unknown- cancel ListenRefused), and the decline rows (sftp placeholder, host-key mismatch hard-decline, server-refused direct-tcpip with the connection staying usable). Fixture shape decided + recorded in the task file: per-row spawned sshd, minted ed25519 host key, publickey-only knobs, the decrypt-at-dial vault posture, the case-1 pin posture. No src/ changes; every row check-gated skipped-with-reason and deadline-bounded. Verification: `scripts/matrix.sh` green (cargo test per build none/git/ session/tunnel/sftp/--all-features — e2e_client rides 2/2/3/5/2/7 rows, clippy x2 `-D warnings`, fmt, doc), plus 10 consecutive all-features e2e_client runs green. taskgraph validate: 42 tasks valid. |
||
|
|
567a34a5e1 |
test(e2e): server-direction real-tool e2e rows (w5-e2e-server)
The POC-3 gates re-landed against the shipped crate in a dedicated tests/e2e_server.rs binary (reusing tests/common; the mapping binaries keep their single-writer posture): OpenSSH pty/shell + pipe exec, -L/-R/-D+curl (the -R arrival leg and -D's direct-tcpip leg end to end), git clone AND push (rev parity + CAS row), and six decline-observability rows (unfeatured pty/-L/-R from real clients, the sftp subsystem decline, the unknown-repo collapsed generic denial — each pinned by its door-side decline event). All rows are check-gated skipped-with-reason and deadline-bounded. E2e-graph fixes (no src/ changes): per-row key workdirs, an in-process port allocator below the ephemeral range, the fabric registration settle, and event_capture::captured_with + support::decline_event (poll-within-row-deadline event observation). Verification: scripts/matrix.sh green (cargo test x6 builds, clippy x2 -D warnings, fmt, doc); 10 consecutive all-features e2e runs green. taskgraph validate: 42 tasks valid. |
||
|
|
d5be18baed |
test(mapping): complete the decline-matrix contract suite (w5-decline-matrix)
- coverage audit recorded in the task file: every mapping.md row × every build it can appear in × both directions → the pinning test - gap rows landed in tests/dispatch_core.rs: the streamlocal_forward decline, the cancel-form refusions, the git-build pty decline - never-features pinned in every build both directions; the auth-agent open recorded pin-bound upstream (russh 0.65 declines AgentForward opens pre-handler; no client initiator at the pin) - sftp-only build verified no-op + declining via the matrix run - matrix runner landed: scripts/matrix.sh (cargo test × 6 builds, clippy ×2 -D warnings, fmt, doc), joined AGENTS.md verification list Verification: scripts/matrix.sh green end to end |
||
|
|
f0c4127c28 |
docs(tasks): decompose wave 5 — validation & release
- Seven task files under tasks/implementation/: w5-decline-matrix, w5-e2e-server, w5-e2e-client, w5-fuzz-harness, w5-release-readiness, review-wave-5 (pure review), w5-publish (over the gate) - Four independent implementation tasks (disjoint files) over the landed waves; release readiness over all four; the gate stays a pure review; the publish is its own task (the alkgit pattern) - Interpretations recorded: gate-then-publish split, e2e vehicle placement, matrix-runner vehicle, fuzz exposure shape, the crate-qualified alkssh-v0.1.0 tag, no version bump (0.1.0 is the manifest value) - Plan doc: wave-5 row -> decomposed + the decomposition record Verification: taskgraph validate (42 tasks) clean |
||
|
|
671a779ce3 |
docs(review): close the wave-4 review gate — verdict green (review-wave-4)
- Gate reading recorded line by line: client.md (dial surface, op rows, acceptor, lifecycle primitives), ADR-003 symmetry, ADR-005/ OQ-05/OQ-07, bounded resources, tracing, smokes, docs honesty — all confirmed - Wave-3 gate booked items 1-4 verified discharged; item 5 verified still booked with its owners (nothing absorbed) - Inline fix: the accepted-open handoff counter now releases on completion or unwind (HandoffCapGuard panic-safety; a panicking assembly acceptor can no longer strand a held slot) + unit row - Inline fix: lib.rs crate-level wave-state doc sentence (stale over w4-lifecycle) - w4-reverse-listen / w4-lifecycle Summary placeholder lines filled Verification: cargo test (default / --all-features / --features tunnel) all green; clippy x2 -D warnings clean; cargo fmt --check clean; cargo doc --no-deps 0 warnings; taskgraph validate (35 tasks) |
||
|
|
dec644e653 |
feat(client): land the lifecycle primitives — listener handles + the clean-disconnect boundary (w4-lifecycle)
- src/client_accept.rs: the ReverseListener handles (ReverseEndpoint carried; cancel() the wire-honest teardown) returned by the -R listen rows; the shared cancel paths both the handles and the stateless wire forms compose into; the confirmed cancel releases the listener count exactly once; no Drop hides a wire op - src/client.rs: the SshConnection internals behind the Arc (ConnectionShared) — the handles' independent cancels compose on the same stack; the §Lifecycle posture module docs (primitives-not-policy, OQ-12; the abrupt-drop/clean-close facts, row-pinned) - rows: drop-handle no-wire-state, handle-cancel + budget release, cancel-mid-data-leg (tunnel build) + the every-build abrupt-drop row; existing reverse rows adapted to the handles; the unix cancel rides the handle flavor - docs/architecture/client.md: the §Lifecycle primitives landing block; task file filled Verification: cargo test (default / session / git / tunnel / all-features) all green; clippy ×2 -D warnings clean; fmt clean; cargo doc --no-deps clean |
||
|
|
7e5989f574 |
feat(client): land the -R leg — the acceptor registry + the reverse-listen rows (w4-reverse-listen)
- src/client_accept.rs: the assembly-mutable acceptor registry (per-type fixed slots under `tunnel`; refuse-never-replace install; payload = the `TunnelChannel` duplex + the open's echo/originator fields — the harness `ForwardedAcceptor` vocabulary grown), the reverse-listen rows (bound-port surfaced; the listen/cancel forms; the runtime-Unix gate at the listen), and the accepted-opens' spawned handoff tasks. - src/client_handler.rs: the `forwarded-tcpip` / `forwarded-streamlocal` hooks route through the registry at arrival time — registered + admitted → accept + handoff; unregistered/unfeatured/cap-exhausted → the clean `AdministrativelyProhibited` decline (never accept-and-drop); pre-install arrivals decline, never buffered. - src/client.rs: the dial wires the empty registry into the handler + the connection (`SshConnection::acceptors`, the late-install accessor) + the listener budget's dedicated counter. - src/error.rs: `OpError::NoAcceptor` / `ListenRefused` / `ListenCapExceeded`; `RequestDenied` → `ListenRefused` (the listen/cancel forms' wire decline class; the port-0 mint rides alone at this pin — an explicit port's bound *is* the requested one, surfaced). - tests/common/sshd_double.rs: the fake `sshd`'s `-R` half — the listen/cancel hooks bind real sockets (the out-param mint is the real bound port), the accept loops open the server-initiated forwarded channels + pump per accepted connection, cancels match by the raw wire strings and tear the listener down. - tests/client_reverse_listen.rs: the contract rows (listen + data leg, the mint, the pre-wire gates, the declines, late-install on/off, the cancels, the ceiling, the unix twins). - tests/tunnel_mapping.rs: the full-circle client ↔ server-direction smoke (both `-R` legs through the alkcall fabric double); real- `sshd` `-R` stays wave-5 e2e (recorded). - client.md: the acceptor's landing record (shape, cap relation, gates, unix-gate placement, port-0 parity). - fix(w4-op-rows, test cfg): the test mod's `family_exit_code` import gated to the exit-consuming builds (`--features tunnel` did not compile its tests before). Verification: cargo test (default / each feature / --all-features) green; cargo clippy --all-targets -- -D warnings (none + --all-features) clean; cargo fmt --check clean; cargo doc --no-deps clean (default build; all-features carries only the pre-existing client_ops private-link warnings). |
||
|
|
2371c2e169 |
feat(client): land the op rows — exec/pty, direct-tcpip, git exec, sftp placeholder (w4-op-rows)
The family op → vanilla translation surfaces on SshConnection, per client.md §op rows: - session: exec (pipe) / pty_exec / shell → RemoteSession — the alktty chunk plane both directions, the exit consumed in-band as the family exit chunk (exit-chunk-is-last); the POC-3 §D.3 mechanics pinned (exec() + wait()-drain, split-halves pump for exit-consuming rows, typed-open-then-straight-into_stream for the exit-less tunnel rows) - tunnel: tunnel_direct / tunnel_direct_unix → TunnelChannel — typed opens, rejections mirrored to OpError::OpenDeclined (OpenDeclineReason), the connection stays usable after every decline (row-pinned per class); the OQ-11 runtime-Unix gate mirrored - git: git_service(Service, repo_id) → GitPackSession — the shared grammar's construction arm in dispatch.rs (parse round-trips unit-pinned), the raw pack duplex pumped, the exit consumed - sftp: the every-build OQ-13 placeholder (OpError::SftpNotServed, Infallible Ok type), before any wire op - shared exit-currency module src/exit_code.rs (extracted from tty_map.rs): SIGNAL_NAMES + derive_exit + sig maps two-direction, new signal_code_from_name client arm - OpError taxonomy (closed, family-mirrored) in error.rs; the fake sshd double grown with the op-row script + observations; tests/client_ops.rs — 18 rows + the alkssh↔alkssh exec smoke (real-vanilla-sshd smoke named wave-5 e2e territory) Verification: cargo test (default + per-feature + all-features) all green; clippy ×2 -D warnings clean; fmt clean; cargo doc --no-deps warning-free. |
||
|
|
0d3fd48f5d |
feat(client): land the dial surface — the authenticated connection handle + the fail-closed client handler skeleton (w4-dial)
The client direction's substrate (client.md §The dial surface), the mirror of w2-server-mount: dial(DialConfig) runs the fail-closed four-step sequence — decrypt-at-dial key load (every KeySource posture a distinct no-dial outcome), the bounded russh client Config from Limits (keepalive 15 s / 3, the wall clock always Some, window/ packet/buffer verbatim), the injected host-key policy at check_server_key (RejectAll connects to nothing; UnknownKey maps to DialError::HostKeyRejected — its only producer), and exactly one publickey-only auth attempt — and returns the authenticated SshConnection handle everything else in the wave rides. - DialConfig amendment (the sanctioned course-correction, recorded): the required `username` field — the USERAUTH_REQUEST's user string; no crate default, never identity, never logged (ADR-004 decision 5). - The client Handler skeleton (src/client_handler.rs): every server_channel_open_* hook overridden explicit-over-inherited with a clean AdministrativelyProhibited reject (never russh's accept-and-drop default, ADR-003 §4); should_accept_unknown_ server_channel the explicit false; decline events on the crate vocabulary (no-acceptor / never-feature reasons). - The in-process fake sshd (tests/common/sshd_double.rs): the client-direction contract vehicle — a plain russh server on a real loopback TCP listener, accept-set identity resolution, the auth_ succeeded handle carrier for the server-initiated-open rows, the per-session outcome channel for the clean-close rows. - tests/client_dial.rs: 10 contract rows — dial success, the five key-source no-dial postures, RejectAll / pin-mismatch / case-2 policy rows, refused auth, the malformed-address dial-time refusal, the bounded-config observable, the six-hook decline wire rows with decline events, and the smoke: alkssh-client ↔ alkssh-server over a real TCP loopback (real-sshd dial stays wave-5 e2e). Verification: cargo test + per-feature + --all-features (14 binaries, all green), cargo clippy ×2 (-D warnings), cargo fmt --check, cargo doc --no-deps — all clean. taskgraph validate: 35 tasks valid. |
||
|
|
d300095687 |
feat(tunnel): ListenRouter token-collision pre-check — refuse, never replace (w4-router-collision)
Closes the wave-3 gate's booked finding item 4 (review-wave-3 §Booked findings): register on an already-held token now refuses fail-closed instead of silently replacing the live route — a collided mint (the ADR-008 discipline's upstream failure) can never steal a live adapter's arrival routes. - ListenRouter::register returns Result<(), String>; collision → Err, the held route untouched (src/tunnel_map.rs) - Error surface decided + recorded: the wire-time collision is the request-time decline disposition (RequestDenied, reason token-collision), not a new AdapterError variant — a global-request hook's Err propagates out of russh 0.65's packet loop and kills the session, while a refused listen's vanilla contract is RequestDenied; AdapterError::ListenRegistration keeps its construction-time class (src/error.rs doc records the sibling) - listen_request_dispatch: register moved before the bound-port out-param write; on refusal the listener cap releases, the uniform decline event emits, and the minted fabric listener tears down immediately (never a half state) (src/handler.rs) - Wire row a_collided_mint_is_refused_and_never_steals_the_live_route pins the full shape through a mint-strategy construct on the fabric double (MintStrategy::WithCollisions — the test forces what the fabric's discipline prevents): second register refuses, first route still serves end to end, refused endpoint never serves, count released (tests/tunnel_mapping.rs) Verification: cargo test (default + git/session/tunnel/sftp + --all-features) · clippy ×2 -D warnings · fmt --check · cargo doc --no-deps · taskgraph validate ✓ (35 tasks) |
||
|
|
0506f233fc |
test(tty): wire-witness the channel_close cancel path + the no-exit-chunk close shape, record the write_all no-deadline limit (w4-tty-coverage)
- wire row: a client close on a served pipe row fires the per-channel cancel (kill-on-drop witnessed before teardown, no exit report fabricated, ended lifecycle event) and releases the cap (over-cap open refused at 1, re-open admits + routes after the close) - wire row: the Closed no-exit-chunk close shape, constructed through the real path (an oversized shim chunk dies at alktty's own ChunkWriter validation -> bridge EOF, scripted exit never forwarded, plain close, nothing fabricated); Break's frame classification pinned by a unit row (wire-unreachable through the validated writer at this pin - recorded) - module docs: the stdout write_all no-deadline known limit recorded (the gate's disposition: bounded backpressure, inactivity-timeout liveness; a deadline would be a behavior change) Verification: cargo test (default/session/session+git/git/tunnel/sftp/ --all-features, tty rows 5x repeat), clippy x2 -D warnings, fmt, doc, taskgraph validate |
||
|
|
3d3bb5e9bf |
test(mapping): land the channel-not-held guard's wire rows on all three served rows (w4-channel-not-held-rows)
- git row: a second exec on a served git channel answers channel_failure inline — the serve rides on, no second serving path; race-free by construction (advertisement drained, serve parked at the gated list_refs) - tty rows: the pipe row (second exec) and the shell row (second shell request) — the same inline decline shape, channel still usable - the rows assert the decline event's exact request vocabulary (class request, reason channel-not-held, route git/pipe/absent, debug level, self-describing build) — the class's vocabulary coverage - tests/common/event_capture.rs: the observe-suite's capture convention shared across the mapping binaries - test-only: no production change; the guard held its documented shape in every featured build (verified default / git / session / session,git / tunnel / sftp / --all-features) Verified: cargo test (+ per-feature + --all-features) all green · cargo clippy --all-targets -- -D warnings ✓ · cargo clippy --all-features --all-targets -- -D warnings ✓ · cargo fmt --check ✓ · cargo doc --no-deps 0 warnings · taskgraph validate ✓ (35 tasks) |
||
|
|
9cf5bfbaf3 |
fix(git): status-before-close by construction — the split-halves seam duplex (w4-git-exit-close)
The wave-3 gate's booked finding 1: the git row's exit-report order was honored by scheduling, not by construction — into_stream()'s ChannelCloseOnDrop spawned a CHANNEL_CLOSE send racing the exit_status_request send for a queue position on russh's bounded message queue (event_buffer_size, default 10); a full buffer at the serve tail could silently no-op the report. - the seam now rides a split-halves duplex built from russh 0.65's public API (its ChannelRx/ChannelTx are private): the reader is git_map's ChannelHalveReader (owned read half, the private rx's observation semantics repinned, no Drop path ever messages the wire) and the writer the write half's ChannelTx via make_writer_ext (poll_shutdown still sends EOF; the Rust 2024 capture phantom erased through a trait object) - the report's send is now the sole close-contracting sender, ordered ahead of the explicit close by sequential awaited sends on the one handle — saturated buffer or not - new wire row backlogged_seam_tail_still_reports_exit_status_before_close: a volume flood freezes the armed client loop (the new harness DataGate + the client data hook russh awaits inline) so the server's flush pends and the bounded message queue saturates at the serve tail; the gate's release drives the tail through the unwinding contention — the row pins exit-status(0) before close under the saturation and falsifies the old shape deterministically (15/15 red under a temporary into_stream() revert, 10/10 green with the fix) - doubles gain a race-safe per-repo serve gate (install_gate, the double-check pattern over list_refs); harness gains drive_with_data_gate (the drive* bodies factored into mount_and_connect, existing signatures unchanged) - all existing git wire rows unchanged and green (wire order identical when the race does not fire); spawn-and-return intact; tty rows untouched; event_buffer_size stays russh's default (pinned in the row) Verification: cargo test (default), --all-features, and six mixed per-feature builds green; clippy x2 -D warnings; fmt -check; cargo doc --no-deps 0 warnings; taskgraph validate (35 tasks). |
||
|
|
dba64d65d3 |
docs(tasks): decompose wave 4 — the client direction
Eight implementation tasks + the gate under tasks/implementation/, folding the wave-3 gate's four booked findings in as dedicated tasks (the gate's own disposition — wave-4 material, not wave-3 backports). Hardening set (server-side, independent of the client chain, scheduled first as cheap remediation): - w4-git-exit-close: booked item 1 — the git row's status-before-close by construction (the seam/shape decided + recorded in-task; the saturated-buffer scenario pinned on the wire) - w4-channel-not-held-rows: booked item 2 — the guard's wire rows on git/tty pipe/shell, per build (test-only) - w4-tty-coverage: booked item 3 — the channel_close-fired cancel path + the Closed/Break no-exit-chunk rows + the write_all no-deadline known-limit record - w4-router-collision: booked item 4 — the ListenRouter token-collision fail-closed pre-check Client chain (sequential logistically — one client Handler impl): - w4-dial: the russh client stack + the connection handle + the client Handler skeleton at fail-closed defaults, over an in-process fake-sshd server double - w4-op-rows: exec/pty, direct-tcpip, git exec over the shared grammar, the sftp client-side decline — the POC-3 §D.3 mechanics - w4-reverse-listen: the -R leg — forward requests + the acceptor registry + late-install (the wave's highest-risk task) - w4-lifecycle: the OQ-12 primitives (cancellable handles, clean disconnect, no reconnect policy) review-wave-4 depends on all eight. The gate's item 5 (upstream asks) stays booked with its owners — no task. Plan wave-4 section carries the decomposition record + four flagged interpretations; wave table status updated. Verification: taskgraph validate ✓ (35 tasks) |
||
|
|
930d9c1996 |
docs(review): close the wave-3 review gate — verdict green (review-wave-3)
The wave-3 gate read the landed server mappings against mapping.md/ADR-006/ADR-003/auth.md line by line: session/pty/shell/pipe (translation bridge + the derivation contract rules 1-6), git exec (full grammar, no-oracle validation, collapsed denial byte order), route-1 bridge direct rows, the full -R listen flow + forwarded-tcpip data leg, per-build decline matrices, identity observed at every seam, bounded bookkeeping, and the uniform safe tracing vocabulary. All four decomposition-time interpretations confirmed. Fixed inline (this gate): - install_forwarded_surface pre-checks the tunnel/forwarded op — alkcall's register silently replaces an existing entry, so a second adapter over the same shared seam registry silently stole the first adapter's arrival routes; now a failed construction (AdapterError::ListenRegistration), pinned by a new wire row - git take drops a stored pty entry (combined-build stale state) - doc hygiene: channel_cap release ordering, the session-row scoping of the handler Drop, the cancel-order recording, the post-cancel arrival caveat, the exit-chunk ADR lineage cross-ref, the stale lib.rs wave-state doc, the superseded dispatch test name, and mapping.md's listen-resource wording (address verbatim + tcp port) Booked for wave 4: the git row's status-before-close honored by scheduling not construction (needs a seam/shape change), the channel-not-held wire test rows, the tty coverage minors (cancel path, Closed/Break shape, stdout write deadline), and a defensive ListenRouter token-collision pre-check. Verified: cargo test (default) + --all-features (173 rows, 13 binaries), clippy --all-targets -D warnings ×2 (default + --all-features), fmt --check, cargo doc --no-deps (0 warnings), taskgraph validate (26 tasks). |
||
|
|
3d0253d981 |
feat(tunnel): land the -R listen flow + the forwarded-tcpip data leg (w3-tunnel-listen)
The tunnel branch's other half is served for real against published alktunnels 0.4.1 / alkcall 0.10.1, with POC-3 §C.4 as the template: - tcpip_forward/streamlocal_forward (+ both cancel forms) map the vanilla listen request family-ward through TunnelListener::open on the held ChannelClient (bounded inline dial — the global request's reply + the &mut u32 bound-port out-param are the hook's); the wire bind endpoint rides as the resource string verbatim (OpenSSH client-side normalization parity). A reply without the minted listen token/bound report declines and reaps its fabric listener. - the door serves the fabric's inbound tunnel/forwarded opens: the serve config gains TunnelListenSeam (the seam-config second course-correction — the shared ServingConfig registry the assembly wired at from_connection_with_serving); alkssh registers the forwarded openable (ChannelCore over a clone of the held client's manager) at SshAdapter::new — a failed registration is a failed construction (AdapterError::ListenRegistration). - the forwarded-tcpip data leg: token correlation through the shared ListenRouter → the in-crate per-session channel cap at the accept surface (the acceptor vocabulary) → Handle:: channel_open_forwarded_tcpip streaming the listen request's own args + the actually-bound port (sshd parity) with TunnelForwardedParams.peer as the originator → pump_bidi(ssh, fabric). Correlation-miss → teardown-at-birth, never a half-open SSH channel. Unix arrivals ride forwarded-streamlocal@openssh.com. - bounded bookkeeping: the per-session listener map packet-loop-only with a dedicated max_concurrent_channels-ceiling counter; the route map bounded by construction (mints at accepted listens, removed at cancels + session teardown — the handler's Drop). - the harness grew the client-side accept hook (server_channel_open_forwarded_tcpip/_streamlocal, acceptor-driven) + a hand-rolled tracing subscriber; 18 contract rows + both real-tool smokes (ssh -L, ssh -R + curl) green in tests/tunnel_mapping.rs; the wave-2 stub listen rows migrated per build (unfeatured / seam-unconfigured). Verified: cargo test across default/git/session/sftp/tunnel and all combos + --all-features; clippy -D warnings (default, all-features, combos); cargo fmt --check; cargo doc --no-deps clean; taskgraph validate (26 tasks). |
||
|
|
6443feac3e |
feat(tunnel): land the route-1 bridge — direct-tcpip + streamlocal served for real (w3-tunnel-direct)
- The route-1 bridge (ADR-006 decision 1, the POC-3 stage-C recipe re-landed as contract): per accepted `direct-tcpip` / `direct-streamlocal` open, the target/substrate are constructed from the SSH open arguments and the spawned task runs `TunnelSession::open_direct` over the held `Arc<ChannelClient>` → accept only after the fabric open succeeds → `take_halves()` pumped against the taken channel (`pump_bidi`) — the open hook returns fast; a fabric failure answers the explicit `ConnectFailed` reject (the wave-2 stub's reason surviving as the real decline; the stub retired from the direct rows, kept on `forwarded-tcpip` for w3-tunnel-listen). - The cap across the fabric window (recorded): the admission is taken at the hook and held across the fabric open; a fabric failure releases the count inside the spawned task — the admitted-but- rejected shape no `channel_close` can ever fire for. `ChannelCap` became a mutex-backed `Arc`: admissions stay packet-loop-only, releases have exactly two disjoint sites (close hook, bridge task), both saturating. - The identity ride recorded (the `w2-auth-bridge` parked item): the alkhub `hub_leg.rs` one-time-set precedent is a serving-side set; the client-side `take_call_connection()` seam is rejected for this door (one-shot detach of the shared assembly handle's open surface — a fortiori the relay note's "wrong for hubs"); the identity the fabric's `AccessControl::check` consults is the dial identity (CF-005), the composition is assembly policy, and the door structurally refuses anonymous serving (`no-identity`, unreachable on the wire). Upstream ask booked for the gate. - The OQ-11 runtime-Unix gate: `direct-streamlocal` compiles wherever `tunnel` compiles; off-Unix a clean open-decline before any fabric call (dial-call witness at zero), on-Unix the real bridge. - Decline classes recorded: policy → `AdministrativelyProhibited` (unfeatured/cap/`no-identity`); cannot-bridge → `ConnectFailed` (failed fabric open, absent handle under the feature, invalid target, off-Unix streamlocal); the event vocabulary gains `invalid-target` and `unix-unavailable`. - The optional dep lands: `alktunnels 0.4.1` (pin re-verified against crates.io — max stable, MIT OR Apache-2.0, rust_version 1.88, alkcall 0.10.1 exact), wired `tunnel = ["dep:alktunnels"]`, `local` never enabled. - The fabric double (`tests/tunnel_mapping.rs`): the POC-3 §C.4 in-process recipe — `ChannelsAdapter` + `InstallChannelZero` + the alktunnels openables + a hand-rolled dial/dial-fail seam (in-process duplex targets keyed by the rendered string; the smoke's dial is real TCP). Rows: both-substrate serve round trips, fabric-failure, cap refusal + release + recovery, the fabric-ACL composed-gate witness, the CF-006 identity witness, the OQ-11 gate, and the `ssh -L` + curl real-tool smoke (conditional-skip posture, ran green). Verification: `cargo test` rows green per feature build (default, tunnel, git, session, git+session, tunnel+git, tunnel+session, --all-features); `cargo clippy --all-targets -- -D warnings` ×2 (default + --all-features) clean; `cargo fmt --check`; `cargo doc --no-deps` 0 warnings; `taskgraph validate` (26 tasks). |
||
|
|
b1e569d1a8 |
feat(session): land the session mapping — the translation bridge + the exit derivation (w3-tty-session)
The biggest single-row surface of the spine: pty/shell/pipe-exec serve
through the published alktty seam (drive_session_pre_negotiated) over a
translation bridge, re-landing POC-3 stage D as contract:
- src/tty_map.rs (session feature): the bridge (SSH data → Chunk::stdin
through a bounded mpsc + framing task; SSH EOF → the zero-length
stdin sentinel — verified against alktty's pump semantics; stdout →
data; stderr → extended_data(1); exit chunk → the derivation
contract), the locally-built negotiation (OQ-08: shell argv verbatim,
wrapper ++ [cmd], cwd/env session-side, ttymodes → the reserved {}
modes JSON), and the spawned serving path.
- serve_tty_row: driven chunks→SSH inline so the exit report is the
path's final act (status before close, ADR-006 decision 4); two
cancel signals (per-channel watch fired by channel_close, connection
teardown fired by SessionHandler::Drop) abort the alktty session
task — the ADR-056 kill-on-drop contract is the cancelled session's
cleanup.
- Derivation contract rules 1–6 in derive_exit + the crate-local
signum→name table: −N → exit-signal(name), −1 → exit-status(255),
core_dumped never, unmapped negatives → 255, the PTY-path rule-5
fidelity limit documented; wire rows pin the shapes.
- Negotiation-error frames (alktty's 0x00-length-prefixed error
framing) → negotiation-failed serving decline + exit-status(255) +
close — the one could-not-start claim this door makes (nothing ran).
- Control rows: window_change/signal → the per-channel BridgeSurface
(bounded mpsc, try_send, never awaited on the packet loop); signal
names verbatim (no decline shape — fire-and-forget); pre-serve
resizes update the stored pty params (final size wins).
- Unset-declines (OQ-08) keep the git row's serving-path shape
(failure then close, spawned): shell-unconfigured /
wrapper-unconfigured / seam-unconfigured; pipe-less shell SERVES
pipe-mode (the recorded vanilla-sshd posture); env_request decline
unchanged (OQ-09).
- tests: the hand-rolled TtyBackend shim + ownership witness doubles;
serving rows through the harness, the unset-field decline matrix,
derivation rows on the wire, negotiation-failure rows, the identity
ride asserted live at the seam, the kill witness; the real-tool
smoke — OpenSSH -tt against a real PTY via dev-dep alktty local
(test graph only), skip-with-reason posture. Wave-2 stub rows
migrated to the new decline reasons (dispatch_core, observe).
Verification: cargo test (113) / --features session (149) / git (142) /
tunnel (115) / --all-features (161) — all green; cargo clippy
--all-targets -- -D warnings ×5 feature configs; cargo fmt --check;
cargo doc --no-deps ×3 builds clean; taskgraph validate.
|
||
|
|
7aac2d11a2 |
feat(git): land the git mapping — the exec grammar + the alkgit dispatch (w3-git-mapping)
- dispatch: the full exec grammar replaces the wave-2 skeleton probe (quoted/unquoted paths, the .git normalization decision, unknown- service / service-only decline-with-reason class; the probe's unit rows migrate with their pinned verdicts preserved) - git_map: the git row dispatches serve_duplex_resolved over the channel duplex (repo id validated, never resolved; the exit-status mapping 0/1 decided per POC-3 §A.5; status before close from the spawned task), with the resolved identity riding the seam - handler: the store-and-take channel handoff (stored at the open, taken in the spawned serving task, released at close) and the max_exec_command_len pre-parse cap - config course correction: GitSeam carries the assembly-supplied alkgit Limits (the ADR-009 budget alkgit's contract assigns the assembler) - tests: tests/git_mapping.rs (byte-level process-mode advertisement, ls-refs round + exit 0, the collapsed-denial byte-order + exit 1, the identity-ride authorize-gate witness, the packless CAS push, the grammar/cap decline rows, the sequential-serve store row, and the real-tool git 2.43 clone smoke over a TCP mount with a skip-with-reason posture); dispatch_core/observe rows updated to the new decision set; tests/common/git_doubles.rs (the hand-rolled recording alkgit quartet) Verification: cargo test (default), --features session, --features git, --all-features; clippy -D warnings ×2; fmt --check; cargo doc --no-deps; taskgraph validate |
||
|
|
58d0b86ec5 |
feat(observe): land the decline-path event vocabulary (w3-decline-tracing)
The wave-2 gate's booked finding: observability on the decline paths. `tracing` rides the manifest per the ADR-002 toolkit but nothing in src/ emitted — a decline's "reason" was only the routing decision documented per build. - src/observe.rs: the event vocabulary, adopting the family posture read from alktty/alktunnels/alkcall/alkhttp (free tracing fns, message-last lowercase, flat fields; no info!/trace!, no macros, no spans): feature_build + row + channel_id + decline_class (open/request/serving/auth) + reason + exec_route (exec rows); declines at debug, the serving-path failure arm the vocabulary's one warn - handler.rs: every decline site emits — auth dispositions (non-ed25519 / fingerprint-unresolved / the four unimplemented methods), open-level declines (unfeatured / never-feature / cap-exceeded / tunnel connect-failed stub), request-level capability declines (exec route-aware, subsystem, env, x11, agent, the -R listen forms), and the seam-stub serving declines with the spawned-path lifecycle (started / ended / failed — the shape the wave-3 mappings inherit) - tests/observe.rs: the event-presence pin — a hand-rolled global capture subscriber (the recorded test convention; no event-capture precedent exists in the family), field-matching rows across the default/git/session/tunnel/--all-features builds, and the safety scan (no username, no fingerprint ever in an event) - helper-level exact shapes pinned in src/observe.rs unit rows Verification: cargo test (default, --features git/session/tunnel, --all-features all green), clippy x2 --all-targets -D warnings, fmt --check, cargo doc --no-deps 0 warnings, taskgraph validate. |
||
|
|
270365ea4c |
feat(config): land the wave-3 per-feature family-seam handles (w3-seam-config)
- ServeConfig gains cfg-gated SessionSeam / GitSeam Option fields (whole-Option struct per seam — the shape decision recorded in the task notes): unset → the owned rows decline even though the feature is on, no construction-time validation - SessionSeam carries the alktty backends map + backend selector + ownership provider; GitSeam carries the alkgit backend quartet + caller-owned pipeline semaphore + push_options_negotiated - optional deps alktty 0.8.1 / alkgit 0.1.0 wired feature-to-dep (pins verified against crates.io: licenses, MSRV, shared alkcall resolution); gix-hash 0.28 as dev-dep only (alkgit does not re-export ObjectId for the trait doubles) - per-feature construction/unset-declines/Debug rows in config.rs; accessors session_seam_configured / git_seam_configured; docs state what each seam handle carries Verified: cargo test (per-feature + --all-features), clippy ×2 (-D warnings), fmt, cargo doc --no-deps, cargo tree feature isolation (git-build: no alktty; session-build: no alkgit), taskgraph validate |
||
|
|
d1f755f2d9 |
docs(tasks): decompose wave 3 — the server mappings
Six implementation tasks + the gate under tasks/implementation/, chained sequentially (the mappings replace stubs in the same Handler impl and share the decline-matrix test binaries): - w3-seam-config: the serve-config's per-feature seam handles (alktty backends/ownership/selector; the alkgit quartet/pipeline/ push-options flag) + the optional sibling deps — wave 3's first course correction to the wave-1 config surface - w3-decline-tracing: the wave-2 gate's booked finding — the decline/serve-path tracing vocabulary, landed before the mappings - w3-git-mapping: the full exec grammar replaces the wave-2 probe + the serve_duplex_resolved dispatch + the store-and-take handoff - w3-tty-session: the translation bridge + the exit-derivation contract (rules 1-6, the signum table) - w3-tunnel-direct: the route-1 bridge (accept only after the fabric open succeeds) + set_identity propagation + the capacity-across- fabric-window mechanics - w3-tunnel-listen: the -R listen flow + the forwarded-tcpip data leg (the POC-untested leg; the stop-and-book provision) - review-wave-3: the gate The plan carries the wave-3 decomposition record with four interpretations flagged for the gate (the serve-config course correction, the already-discharged server-side sftp bullet, the w3-tunnel-listen open mechanics, the booked-items' wave-3 homes). taskgraph validate: 26 tasks, clean. Sibling pins verified against crates.io at decomposition time: alktty 0.8.1 / alkgit 0.1.0 / alktunnels 0.4.1 / alkcall 0.10.1. |
||
|
|
5b2654f492 |
docs(tasks): close the wave-2 review gate — verdict green
Gate reading (commits b90e89b..196289f) against the architecture line by line: ADR-001 mount contract, ADR-002 Config construction, ADR-003 feature gates, ADR-004 dispositions, ADR-006 constraints, mapping.md skeleton decline matrix per build, bounded resources, and the harness — all confirmed. All four flagged interpretations confirmed as recorded. Fixed inline before committing (task-file hygiene only): - w2-loopback-harness, w2-dispatch-decline-core: acceptance boxes checked once verified against the landed rows/runs - w2-bounded-resources: stale placeholder lines removed - review-wave-2: verdict recorded Booked finding (next wave's decomposition): decline-path tracing/observability — the reason a given wire decline carried is currently docs-only (tracing unused in src/). Verification: cargo test 104 rows green; --all-features green; clippy ×2 -D warnings green; fmt clean; cargo doc 0 warnings; taskgraph validate 19 tasks. |
||
|
|
196289f5ad |
feat(handler): enforce the in-crate concurrent-channel cap at the open surface (w2-bounded-resources)
- new private channel_cap module: per-session active-channel count; admit (check+increment at accept sites) / has_room / saturating release, single-writer (inline packet-loop hooks), unit-tested - handler wires the cap at every channel_open_* hook: the session accept path admits-and-counts at the accept point; the tunnel rows cap-gate before the seam stub (a counted-but-unopened stub would leak the cap); x11 never consumes a count; the count releases through the new channel_close hook - dispatch::cap_exceeded_open_decline -> AdministrativelyProhibited (the assembly's policy ceiling; OpenSSH MaxSessions parity) - bounded-resources contract rows: cap reached -> clean decline, close -> recovery open succeeds (wire-order deterministic), zero cap refuses without poisoning, exhausted cap answers the tunnel row with the cap reason; max_auth_attempts verified behaviorally (within-ceiling failures keep the advertisement, the past-ceiling attempt is the NoMoreAuthMethodsAvailable teardown); window/packet/buffer/inactivity knobs recorded as covered by the config-mapping tests - max_exec_command_len stays carried, not enforced (wave-3 grammar) Verification: cargo test (default), --features session / git / tunnel / --all-features, clippy --all-targets (-D warnings, also --all-features), fmt --check, cargo doc --no-deps - all green |
||
|
|
2e5a7c9cee |
feat(serve): land the spawn-and-return plumbing + exit/status mechanics (w2-spawn-return)
- src/serve.rs (new): the ADR-006 decision-4 mechanics in code — spawn_serving_path (the one choke point; nothing awaited inline) and report_exit_status_then_close (exit-status before close via the cloned Session::handle()), #[doc(hidden)] contract-test re-exports; a unit row pins spawn-returns-while-pending. - src/handler.rs: the four session-row seam stubs became spawned serving paths (failure-then-close unchanged; stubs are plain fns — an async stub holding &Session across the spawn is non-Send); open- level declines and the tunnel seam stub stay inline by class, per the new module-doc section. - tests/spawn_return.rs (new): two end-to-end wire rows — the client observes exit-status(7) strictly before close and after the handler returned; the handler-returns-fast row routes follow-up traffic while the serving path is still gated (POC-3 deadlock inversion). - tasks/implementation/w2-spawn-return.md — completed with notes. Verification (all green): cargo test; per-feature dispatch matrices (--features git/session/tunnel/sftp, --all-features); cargo clippy --all-targets/-all-features with -D warnings; cargo fmt --check; cargo doc --no-deps (no warnings); taskgraph validate. |
||
|
|
68d3723f6a |
feat(dispatch): land the parse-only dispatch + the fail-closed decline core (w2-dispatch-decline-core)
- src/dispatch.rs: the ExecRoute decision, the skeleton exec-parse probe (service prefix + path shape; wave 3 replaces with the full grammar), the wire-reason shapers (AdministrativelyProhibited for unfeatured/never-feature opens, ConnectFailed for the tunnel seam stub), 7 probe unit rows - src/handler.rs: channel_open_* gates per the feature graph (session ∨ git; tunnel; x11/auth-agent never), parse-only exec_request dispatch before any allocation, session-row routes, request-level declines (subsystem all-names with the sftp OQ-13 and git named classes, env per OQ-09, x11/agent, the four listen requests), the named wave-3 seam stubs; decline-first-alloc-never structural; module docs carry the decline-core contract incl. the inherited auth-agent-open upstream note - tests/dispatch_core.rs: 26 Contract-stamped decline-matrix rows per feature build driven through the loopback harness (none / git-only / session-only / tunnel-only / all-features + shared never-feature rows) - tests/common/harness.rs: extended helper vocabulary (tunnel/x11 opens, pty/env/x11/agent requests, listen requests) — discharges the w2-loopback-harness live-rows note - tests/loopback_harness.rs: the skeleton decline-only session-open row cfg-pinned to the featureless builds it describes Verification: cargo test (91 rows, default build); per-feature dispatch_core rows (git 11 / session 10 / tunnel 4 / all 8 / sftp 3); cargo test --all-features; cargo clippy --all-targets -- -D warnings (and --all-features); cargo fmt --check; cargo doc --no-deps |
||
|
|
3b77193e85 |
feat(tests): land the loopback harness — the in-process contract-test vehicle (w2-loopback-harness)
- tests/common/harness.rs: the recorded transport decision (tokio duplex pair + russh connect_stream; recorded over TCP loopback for determinism, no-port portability) and placement decision (sibling of the wave-1 doubles, the crate's contract-test vehicle convention) - the client-side posture from the doubles: MemHostKeyPolicy bridged into russh's check_server_key (HarnessClientHandler, decision input byte-equal to the wave-4 dial wiring's), the Limits keepalive/ inactivity posture (client_config), fixture-key auth helpers - thin helpers: authenticate_publickey + remaining_methods, open_session_channel (open-failure reason observed in the Err), request_exec/shell/subsystem, next_message / await_exit_status / await_close, drive → LoopbackSession with clean unmount - acceptance proof in tests/loopback_harness.rs: 6 contract-stamped rows, incl. the full in-process connect → fixture-key auth → authenticated-session observation (host-key decision log + observed decline reason) and the fail-closed host-key decline Verification: cargo test (81 pass), cargo clippy --all-targets -- -D warnings, cargo fmt --check, taskgraph validate — all green. |
||
|
|
2202149fc0 |
feat(auth): land the ADR-004 dispositional auth bridge (w2-auth-bridge)
- auth_publickey: ed25519-only gate → single fingerprint path → resolve_from_fingerprint → accept-with-identity (stored per- connection, read by wave 3) / reject fail-closed; the gate is explicit-over-inherited (ADR-002 decision 4) and precedes resolution - auth_publickey_offered: explicit accept-and-delegate (counting stays honest, POC-1) - auth_none / auth_password / auth_keyboard_interactive / auth_openssh_certificate: four explicit family-contract declines - username carried by no hook — resolution is fingerprint-only (OQ-03) - SessionHandler constructible via #[doc(hidden)] re-export (the w2-server-mount note's widening); resolve_bridge structured out of the #[non_exhaustive]-arg hooks so contract tests drive the bridge core directly - tests/auth_bridge.rs: 21 rows — wire rows over the real adapter mount (accept, fail-closed reject, username independence, revoke between attempts, none/password/certificate declines) + direct rows over the wave-1 doubles - cert fixture (throwaway ca + alpha-cert.pub) recorded in fingerprints.txt; MemHostKeySource double in tests/common Verification: cargo test (76 across the suite), --all-features, clippy --all-targets (both feature sets, -D warnings), fmt --check, doc --no-deps, taskgraph validate — all green |
||
|
|
b90e89b600 |
feat(server): land the SshAdapter mount + russh Config construction (w2-server-mount)
- SshAdapter:🆕 decrypt-at-construction host-key load (ADR-005
decision 6) — locked / absent / decryption / unknown-version /
decode all refuse with AdapterError::Construction(KeySourceError
preserved), no adapter; an empty key set refuses as Absent even
from a custom source.
- russh 0.65 server Config built from ServeConfig: methods exactly
{publickey} (MethodSet from [MethodKind::PublicKey] — honest
advertisement, ADR-004 decision 4); host keys ride Config::keys;
Limits knobs (window_size, maximum_packet_size,
channel_buffer_size, max_auth_attempts) one-for-one;
inactivity_timeout always Some (bounded-resources posture). All
other knobs stay russh's default posture: rekey Limits (RFC-4253
defaults, russh-enforced), auth_rejection_time, server_id
(russh default — recorded), server keepalive (client-direction
posture only).
- SshAdapter::handle: ADR-001 mount — generic duplex stream in,
run_stream per stream, run to completion (Result<(), AdapterError>;
no ALPN/relay code; the serving loop is spawned inside russh, so
ADR-006's spawn-and-return holds without double-spawning).
- SessionHandler skeleton (pub(crate)): per-connection
russh::server::Handler with type Error = AdapterError; every hook
at the inherited fail-closed default; carries Arc<ServeConfig> for
w2-auth-bridge / w2-dispatch-decline-core.
- AdapterError (error.rs): Construction + Wire (From<russh::Error>)
— the wave-1 error-model's reserved wave-2 shape.
- Tests: advertisement-exactly-publickey; knob mapping (tuned +
default postures); key-set parity; construction failure matrix;
EOF-on-mount; wire smoke (russh client over duplex: full KEX,
fail-closed auth decline, exactly the {publickey} method list on
the wire).
Verified: cargo test (61 across suites), cargo test --all-features,
clippy --all-targets -- -D warnings (default + all-features),
fmt --check, cargo doc --no-deps, taskgraph validate.
|
||
|
|
18ef55c60d |
docs(tasks): decompose wave 2 — the server stack
Six implementation tasks + the gate under tasks/implementation/, chained sequentially (the auth hooks, dispatch, and channel cap all edit the same per-connection Handler impl): - w2-server-mount: SshAdapter::handle mount + russh Config construction from the serve-config (honest advertisement, host-key decrypt-at-construction, the Limits knob mapping) + the Handler skeleton at fail-closed defaults - w2-auth-bridge: the ADR-004 dispositions (ed25519-only explicit decline, fingerprint-only resolution, explicit declines for every non-publickey method) - w2-loopback-harness: the in-process client/server contract-test vehicle over the wave-1 doubles - w2-dispatch-decline-core: parse-only dispatch + the fail-closed decline core, contract-tested per feature build - w2-spawn-return: the structural constraint pinned by test + the exit-reporting mechanics via the cloned Session::handle() - w2-bounded-resources: the in-crate concurrent-channel cap at the handler surface - review-wave-2: the gate The plan carries the wave-2 decomposition record with four interpretations flagged for the gate (skeleton-level git probe, declining routing stubs, the adapter error surface, the harness transport choice). taskgraph validate: 19 tasks, clean. |
||
|
|
a19cdc4832 |
docs(tasks): close the wave-1 review gate — verdict green
Review task record: the line-by-line ADR/OQ gate reading, the three flagged interpretations confirmed (sibling-dep timing, vault-double shape, policy verdict type), the minor inline fixes, and the green verification summary. Wave 2 decomposition unblocked. |
||
|
|
1bd2588788 |
docs(src): wave-1 gate polish — rustdoc links, doc typos, redundant test lines
- resolve 2 rustdoc broken-intra-doc-link warnings (ambiguous 'async_trait' crate/attribute-macro name) in key_source.rs and host_key.rs - fix stray ')' typo in the HostKeySource docs - rewrite the stale lib.rs architecture line (dependency surface and feature graph are landed, not 'landing with implementation start') - drop a discarded try_sign probe and a redundant None re-assignment in tests Verification: cargo test 47/47 (default + --all-features), clippy -D warnings (both feature configs, all targets), fmt --check, cargo doc --no-deps 0 warnings |
||
|
|
214690b9c2 |
test(doubles): land the wave-1 in-memory seams + unified ed25519 fixtures
The `w1-test-doubles` task: the in-memory seams every later wave's contract tests run against, behind their production seams. - tests/common/mod.rs — the doubles, test-only (nothing reachable from lib.rs, no feature): MemIdentityProvider (registered fingerprints -> alkcall Identity resolutions over the IdentityProvider trait; token path resolves none per ADR-004), TestVault (a wrapper over alkvault 0.1's shipped in-memory construction — resolving the flagged vault-double question: wrap, not re-impl), MemHostKeyPolicy (exact (host, fingerprint) rules + explicit default verdict, fail-closed by default, inspectable decision log), and the fixture table. Module docs record the location/vault/fixture decisions and the row/stamp discipline (greppable Contract stamps). - tests/vectors/ — one unified fixture set: four ssh-keygen-minted ed25519 keypairs (committed .key + .pub + fingerprints.txt recorded from ssh-keygen -lf, OpenSSH_9.6p1). The previous inline PEM constants in src/key_source.rs were rejected by OpenSSH itself (russh-only decodable); all keys were re-minted and the overlapping vector set retired — one fixture set now serves the parity tests, the custody round-trip, and every later auth/mapping test (src-unit tests share it via include_str!). - tests/doubles.rs — the end-to-end rows: register -> resolve, seal -> source -> decode (+ locked/absent/tampered postures), host-key set in order, policy verdicts honored-and-logged. - tests/fingerprint_parity.rs and src/key_source.rs tests moved onto the shared fixture files; src changes are #cfg(test) only. Verification: cargo test (33 + 9 + 5 green; --all-features also green), cargo clippy --all-targets -- -D warnings clean, cargo fmt --check clean, taskgraph validate ok (12 tasks). |
||
|
|
949d0bbbe4 |
feat(config): land the configuration surface (serve-config, dial-config, Limits)
- ServeConfig: OQ-08 shell_argv / exec_wrapper_argv as Option<Vec<String>> with unset-declines doc contracts (no crate default shell/wrapper); OQ-09 session_cwd/session_env (assembly-supplied, never wire-derived); injected Arc<dyn IdentityProvider> + Arc<dyn HostKeySource> handles; optional Arc<ChannelClient> route-1 handle (absent -> tunnel rows decline even under the tunnel feature); Limits. - DialConfig: address + injected Arc<dyn KeySource> and Arc<dyn HostKeyPolicy> handles + Limits. - Limits: the bounded-resources budget model both directions carry — in-crate caps (max_concurrent_channels 64, max_exec_command_len 8192) and the russh-knob mapping with documented postures (max_auth_attempts 10 per ADR-004 decision 6; window 2 MiB / packet 32 KiB / buffer 100; inactivity 600 s always-set shape; keepalive Some(15 s)/3 client posture). - Types only: no russh Config construction (wave 2), no dialing (wave 4); no construction-time validation — unset-declines lives in the type shape (error.rs no-config-error contract). Verified: cargo test (36), cargo test --all-features, clippy (all-targets, -D warnings, default + all-features), fmt --check. |
||
|
|
4d529dbacf |
feat(key-source): land the vault-backed key sources (ADR-005 custody in code)
- Two injected-handle seams: KeySource (dial-config, decrypt-at-use -> one PrivateKey) and HostKeySource (serve-config, decrypt-at-construction -> the Config::keys set); async_trait object-safe, the HostKeyPolicy handle shape - Crate-provided ADR-005 implementations: VaultKeySource (injected vault handle + Option<EncryptedData>, None -> Absent) and VaultHostKeySource (record set, empty -> Absent) over the shared decrypt_and_decode chain: vault.decrypt -> Zeroizing<String> seam wrap -> one-time decode_secret_key(plain, None) - EncryptedData opaque in src/ (no key_version interpretation); vault error taxonomy reshaped into the five-variant KeySourceError matrix, never collapsed; plaintext never persists - 12 in-module tests on the real in-memory alkvault handle: round-trip (fingerprint-identical ed25519 key that signs), full failure matrix, locked host-bootstrap refusal, lock-mid-session lifecycle, v2->v3 rotation identity; three committed throwaway PEM fixtures Verified: cargo test (23 + 4 parity), cargo test --all-features, clippy -D warnings (default + all-features), fmt --check, taskgraph validate |
||
|
|
149f11d4b2 |
feat(host-key): land the HostKeyPolicy seam (OQ-05, policy-blind)
- HostKeyPolicy trait: async, dyn-compatible (Arc<dyn> handle, the alkcall injected-handle pattern) — decide(&self, &PresentedHostKey) -> bool with host/port/fingerprint as the decision input - Verdict type pinned to the russh 0.65 check_server_key shape: bool is the trust decision; decline reasons stay with the policy (declines are dispositions, not errors). Flagged for the wave-1 gate - Built-in default RejectAll: fail-closed reject-all (russh's default, kept); no accept-all option exists in the crate - async-trait 0.1 added for the dyn-compatible async handle (alkcall-precedented; tree-deduped). Flagged for the wave-1 gate - Tests: default rejects; custom verdict honored; both through the dyn handle; the whole presented context reaches the policy Verified: cargo test (14), cargo test --all-features, cargo clippy --all-targets -- -D warnings, cargo fmt --check, taskgraph validate |
||
|
|
22b82af517 |
docs(architecture): correct the fingerprint base64 alphabet wording
The format contract (ADR-004 decision 1) is SHA256: + unpadded standard base64 — not url-safe. The ssh-key re-export and ssh-keygen both emit the +/ alphabet (visible in the recorded fingerprints); the 'url-safe' wording in ADR-004/auth.md/plans/implementation.md and the POC-1 §1 finding was a misnomer. Wording corrected; the parity contract itself unchanged on both sides. |
||
|
|
635e82b54b |
feat(fingerprint): land the single fingerprint path + ssh-keygen parity vectors
- src/fingerprint.rs: alkssh::fingerprint wraps the ssh-key re-export (HashAlg::Sha256) — the crate's only fingerprint computation and formatting site (ADR-004 decision 1's contract invariant) - tests/fingerprint_parity.rs: 4 committed ed25519 fixtures with ssh-keygen -lf-recorded fingerprints (OpenSSH 9.6p1, independent issuer); blobs cross-checked against the vectors manifest - verified: cargo test, clippy (default + all-features, -D warnings), fmt; taskgraph validate clean |
||
|
|
d85299df38 |
feat(error): land the fail-closed key-source error taxonomy
- KeySourceError with exactly five variants (Locked, Absent, Decryption, UnknownKeyVersion, Decode) — ADR-005 decision 4's failure matrix as distinct shapes, the vault taxonomy reshaped never collapsed; no catch-all, no Other/Unknown - payload shapes pinned: Locked/Absent payload-free; Decryption/UnknownKeyVersion carry the vault's opaque messages; Decode carries a message so the RC-pinned ssh-key error type never enters the public API (flagged for the wave-1 gate) - each variant doc-commented with ADR-005 semantics + fail-closed consequence; module stays engine-blind (no vault/russh types) - no From<VaultServiceError>: non-mappable vault variants would force a collapse; w1-key-sources maps explicitly at its seam - exhaustive-match + Display/Debug/Clone unit tests Verified: cargo test (default + all-features), cargo clippy --all-targets --all-features -- -D warnings, cargo fmt --check, cargo doc --no-deps, taskgraph validate |
||
|
|
78ae905b21 |
build(manifest): land wave-1 dependency set and ADR-003 feature graph
- russh = "0.65", default-features off, aws-lc-rs only (ADR-002: no flate2, no rsa; tree RC-free except the recorded ssh-key 0.7.0-rc.11 pin riding the russh keys path) - alkcall 0.10, alkvault 0.1 version-pinned; no path deps - standing toolkit: tokio, thiserror, tracing, parking_lot, zeroize - edition 2024, rust-version 1.89 - ADR-003 feature graph: session/git/tunnel/sftp, default = [], flat and independent — features gate nothing yet (wave-2 decline core is the first consumer) - task w1-manifest completed; notes/summary filled Verified: cargo tree (no rsa/pkcs1/flate2), cargo test, cargo test --all-features, cargo clippy --all-targets -- -D warnings (default + all-features), cargo fmt --check, taskgraph validate |
||
|
|
0b1bc284fa |
docs(plans): add wave-based implementation plan; decompose wave 1
- docs/plans/implementation.md: five-wave decomposition of the architecture (foundations -> server stack -> server mappings || client direction -> validation & release), following the alkgit sibling pattern (decompose-implement-gate rhythm, per-wave decomposition records, review gates at wave boundaries) - scope authority: v0.1.0 publishes the crate; assembly wiring stays out; sftp decline placeholder is shipped behavior - wave 1 decomposed into eight task files under tasks/implementation/ (manifest, error model, key sources, host-key policy, fingerprint, config surface, test doubles, review gate) with recorded interpretations flagged for the gate Verification: taskgraph validate (12 tasks, no cycles); cargo test, clippy -D warnings, fmt --check green |
||
|
|
ab6fb9de87 |
docs(architecture): resolve all open questions (OQ-01..13), mark specs reviewed
- OQ-08: shell convention = assembly-supplied serve config (shell argv for shell_request; exec wrapper argv + command string for exec; no crate default; unset -> clean decline) - OQ-11: direct-streamlocal = universal compile + runtime Unix-gated decline (channel and listen flow) - OQ-10: closed as contract + upstream constraint; derivation rules formalized in mapping.md (Exit chunk -> SSH exit reporting), incl. the -1/genuine-SIGHUP fidelity tradeoff and an unmapped-negative fallback rule - OQ-01/04/09/13: resolved as fully-decided dispositions (ADR-001/ ADR-004/fixed decline postures); remainders were forecasts about consumers, not pending alkssh decisions; upstream deps now listed as watch items, not OQs - OQ-12 restored to the tracker (dropped in the rewrite) - review-agent criticals/warnings fixed: exec-unset + wrap mechanics specified, streamlocal listen gate added, stale deferred/tracked language removed, ADR-006 title fixed, 0.64.1-vs-0.65.0 crosswalk added in ADR-004, ADR frontmatter aligned to 'reviewed' All specs + ADRs 001-006 now reviewed; implementation may begin. |
||
|
|
f36c268694 |
docs(architecture): OQ-10 reopened — premise corrected in place, derivation details deferred to mapping.md
- OQ-10 is not resolved: the mapping-contract derivation details (name
table, core-dumped disposition, −1 disposition) are working
dispositions on mapping.md's exit rows until formalized there / as an
ADR-006 amendment. The
|
||
|
|
1a40bfb498 |
docs(architecture): OQ-10 resolved as a decided mapping contract; tracker statements made accurate
- OQ-10 resolved: alkssh derives SSH exit-signal from the alktty exit chunk's negative code (−N = signal N, pipe path) server-side; −1 (alktty's could-not-determine sentinel) → exit-status(255), never a signal claim; core_dumped never asserted in v1; no alktty wire change - new verified backend finding recorded: portable-pty 0.9 reduces PTY-mode signal termination to a plain exit code 1 (signal number unrecoverable through its trait surface) — faithful exit-signal on the PTY row is an alktty/portable-pty upstream ask, not an alkssh one - research docs (poc-3 §D.2, pocs.md, mapping-table rows) restated accurately instead of carrying inline correction scaffolding Verification: cargo doc --no-deps earlier this session (docs-only change) |
||
|
|
9217a8b3cf |
docs(architecture): OQ-10 premise corrected — the negative exit code IS the signal identity
- OQ-10's seed overstated alktty's exit-chunk design as a 'signal-identity gap': the negative code is the documented signal encoding (alktty ADR-004, ported from alknet ADR-055; src/local/pipe.rs::exit_code_from — Unix ExitStatus::signal() → -sig), a tty/pty-grounded convention - 'alktty widens the chunk' demoted from co-equal candidate to gated exception: it would widen ControlMessage::Exit's one-way parsed shape, the backend-wide exit_code API, and the alknet ADR-055 lineage — requires an alktty ADR with explicit tty/pty-grounded justification, never an alkssh convenience ask - OQ-10 re-scoped to the real remaining detail: −1 (wait failure vs SIGHUP) collision, core-dumped disposition, non-Unix semantics, name derivation - correction notes added in place: poc-3 §D.2, pocs.md, mapping-table.md (both rows); mapping.md exit_signal row updated Verification: cargo doc --no-deps (docs-only change) |
||
|
|
ff2d69c67b |
docs(architecture): ADR-002 amendment — the key-algorithm position is ed25519-only
- ADR-002 decision 4 retitled and strengthened per review input: not 'rsa off, ed25519-first' but ed25519-only. Rationale recorded: one key algorithm carries the family's identity — same ed25519 keys behind alktls self-signed certs, iroh, and the downstream alknet rewrite (ssh + ed25519, tcp + tls, quic + tls, iroh); legacy schemes (rsa/dsa/ecdsa/sk) are issued, stored, or derived nowhere in the family - operational consequence: auth_publickey declines non-ed25519 keys explicitly (ADR-004's explicit-over-inherited pattern), so the posture survives pin bumps; registration is ed25519-only by family posture, not assembler choice - ADR-004: carries the matching dated amendment note; other dispositions unchanged - auth.md: disposition summary matches (ed25519-only) - research/russh.md: pin-question list marked resolved by ADR-002 Verification: cargo test, cargo clippy --all-targets -- -D warnings, cargo fmt --check — all pass |
||
|
|
263f91fdb7 |
docs(architecture): review-pass cleanup — inconsistencies, stale refs, scaffolding refresh
- OQ-02: rewritten as a clean resolved statement; contradictory deferred-table row removed - OQ-05: 'old-code anti-pattern' framing corrected — ephemeral-fleet accept-and-continue is a legitimate case-2 HostKeyPolicy; the crate rule is 'no hard-coded trust model', not 'accept-all forbidden'; dangling 'expected ADR-007' pointer replaced with the real contract locations (auth.md, client.md) - README: OQ counts corrected (6 resolved / 3 open / 4 deferred); sibling references normalized to real ADR IDs (alkcall 010 is alkhttp 014's analog; 'ADR-016-analog' was a wrong number) - client/auth: OQ-07 status corrected to resolved-at-adapter-level; TOFO typo; TOFU framing - ADR-004: dangling 'ADR-008' -> serve-config defaults - ADR-006: SOCKS5 typo - mapping: alktty typo; '(OQ-… note below)' placeholder -> the real POC-3 note reference - research: cross-repo numbers qualified (alktty OQ-44, alkvault ADR-021); reverse-runner lesson 5 reframed per correction - scaffolding: Cargo.toml description/comments and src/lib.rs doc comment updated against ADR-002/003/006 (dynamic-forward retired, git exec-only, russh pin decided); CHANGELOG logs POC completion and ADRs 001-006 Verification: cargo test, cargo clippy --all-targets -- -D warnings, cargo fmt --check, cargo check — all pass |