Review untangle (three layers: local door / wire protocol / fabric): - vanilla ssh -D binds its SOCKS5 proxy client-side (the local door — alksocks' local-door shape) and opens plain direct-tcpip per connection: the server needs nothing SOCKS-shaped (tunnel row + route-1 bridge cover the leg; POC-3 correction 2 already said this) - the family-native SOCKS5-in-channel server row served no consumer of ours: family apps compose alksocks' local door locally over the alkssh client's direct-tcpip DialFn — alkssh doesn't depend on alksocks on this path - amendments: ADR-003 §1a (feature removed), ADR-006 point 3 (SOCKS- detect retired; session dispatch is parse-only; request-less session channels decline), OQ-02 resolution updated with the simplification, mapping-table.md rows annotated (research doc keeps POC-3 stage B as the re-add feasibility record)
6.4 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-10-26 |
ADR-003: The capability feature graph — flat, independent, two-direction
Status
Accepted — 2026-10-26. Builds the fail-closed capability contract (family ADR-003 decision 3) into the crate's own feature names; the decline paths are contract territory on every side of every feature.
Context
mapping-table.md (POC-3-validated) split the SSH session stream
into capabilities by what rides it (shell/exec, git, pipes), and
vanilla SSH multiplexes tunnel legs through direct-tcpip/forwarded-tcpip
channels (a vanilla ssh -D is its own client-side SOCKS door plus
direct-tcpip — the wire never sees SOCKS, POC-3 correction 2). The feature
graph is how alkforge gets an endpoint that is structurally incapable
of serving what it didn't enable: a git-only build accepts no shell and
no tunnels — and, in the opposite direction, no capabilities a client
build didn't opt into.
Phase 0 validated the seams end to end (POC-3 stages A–D: git exec,
direct-tcpip via the alktunnels bridge, session/pty, all against
published sibling crates — with stage B additionally establishing,
against real vanilla ssh -D + curl, that the wire-level SOCKS story
is client-side; its SOCKS-in-channel serve path was subsequently
retired, see §1a), and the user's direction confirmed the scope: full
feature set except sftp (placeholder until alkfs lands).
Decision
-
The feature set (cargo features on
alkssh, all off by default):Feature Serves (inbound) Consumes (outbound) Family seam sessionsession channels: pty/shell, pipe-mode exec client pty/shell/exec/subsystem opens alktty gitgit-parseable exec (dispatched out of session channels) client git consumption of upstream servers alkgit tunneldirect-tcpip,forwarded-tcpip,direct-streamlocal, thetcpip_forward/streamlocal_forwardlisten flowclient direct-tcpipopens (which is what-Drides — see 1a)alktunnels sftpplaceholder — declines until alkfs lands (OQ-13) client sftp half, also deferred alkfs (future) 1a. No
dynamic-forwardfeature. Amended 2026-10-26 (review untangle): the earlier graph carried adynamic-forwardfeature with a server-side SOCKS5-in-channel row — a family-native client shape ("speak SOCKS into a session channel, server dials through alksocks"). No consumer of ours is that client: vanillassh -Ddoes its SOCKS5 handshake client-side and opens plaindirect-tcpipchannels (POC-3 correction 2), and the family behavior of-Dis a client-side local door — alksocks' local door given aDialFnbacked by the alkssh client'sdirect-tcpipopens, composed at the assembly. The server needs only thetunnelrow for the whole-Dleg, and alkssh does not depend on alksocks on this path. The SSH-detect dispatch from ADR-006 point 3 retires with it (session dispatch is parse-only; a request-less session channel declines). If a deferred future (OQ-01's relay/browser leg) ever produces a client that genuinely wants a remote SOCKS door, adding it back is an explicit ADR — the two transports are "just byte streams", so nothing forbids it; nothing needs it. -
Flat and independent. No feature implies another.
default = []— a default build has no capabilities at all; every unfeatured channel type/open/request declines with a clean error.gitdoes not implysession(a git-only build serves the git-parseable exec path by declining to parse to anything else — the parse-then-fail contract from POC-3 stage A). Note thetunnelclient leg's SOCKS door is local to the dialing host (alksocks composed at the assembly), not a wire capability — the wire seesdirect-tcpiponly. -
Two-direction symmetry. Each feature name gates both the server-direction (what an embedded alkssh serves vanilla clients) and the client-direction (what a family app drives when dialing an upstream SSH server, including the forwarded-channel acceptor rows). One name, one capability, both doors — this is the symmetry rule, now structural: a
git-only build cannot dial a tunnel upstream any more than it can serve one. -
Client-side fail-closed at the acceptor. Server-initiated channel opens (
forwarded-tcpipis the real one — the-Rdata path) are served only under a registered feature's acceptor handler; unhandled/unfeatured accepted opens are declined, not accept-and-dropped (russh's client default is accept-then-drop — ours to fix; the reverse-runner extraction names it). -
Never features.
x11andauth-agentforwarding are not feature-gated at all — always declined, at any configuration (agent forwarding is the anti-pattern of the alkvault custody model; x11 has no family seam). An ADR amendment is required to ever feature-gate them. -
Honest advertisement per build. What the wire advertises mirrors what features are on: auth
methodsis always{publickey}(ADR-004) server-side; client-side capabilities are invoked only by the assembly, and unfeatured rows decline. There is no "advertise and see" path.
Consequences
- Positive: alkforge's ssh endpoint shape is a straight cargo selection; the failure matrix (git-only, session-only, none) is a tested decline matrix rather than a hope; the client and server surfaces can't drift apart on capability naming.
- Negative: the split of
sessionmakes feature interplay a real test matrix (per-feature decline paths on both directions are contract tests — the AGENTS.md verification convention covers this via--all-featuresruns plus the decline-matrix suite). - Neutral: feature names are now contractual (downstream builds specify them); renames would be semver-visible — hence the ADR, not incidental naming.
References
mapping-table.md(the validated rows under these names + the feature-split notes + the three POC-3 corrections)- Family ADR-003 (decision 3 — the fail-closed capability features), family ADR-002 (why exclusion matters for alkforge)
docs/research/reverse-runner.md(client-direction surface the symmetry rule serves; the acceptor default fact)docs/research/poc-3-mapping-bakeoff.md(stages A–D: the seams at these names work end to end)- OQ-13 (sftp placeholder tracking), OQ-02 (dispatch precedence
inside
session)