docs(architecture): retire the dynamic-forward feature — the -D untangle

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)
This commit is contained in:
glm-5.3-flash committed 2026-10-26 15:32:08 +00:00
1 parent e9f78bf6f7
commit c7ab19ed8a
4 files changed
+76 -46

No files matched your search

@@ -13,18 +13,23 @@ decline paths are contract territory on every side of every feature.
## Context
`mapping-table.md` (POC-3-validated) split the SSH `session` channel
one channel type carries shell, exec, git, and SOCKS traffic, and
vanilla SSH multiplexes them all through session channels. The feature
`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, no
tunnels, no SOCKS — and, in the opposite direction, no capabilities a
client build didn't opt into.
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,
SOCKS-in-channel, direct-tcpip via the alktunnels bridge, session/pty,
all against published siblings) and the user's direction confirmed the
scope: full feature set except sftp (placeholder until alkfs lands).
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
@@ -35,18 +40,37 @@ scope: full feature set except sftp (placeholder until alkfs lands).
|---|---|---|---|
| `session` | session channels: pty/shell, pipe-mode exec | client pty/shell/exec/subsystem opens | alktty |
| `git` | git-parseable exec (dispatched out of session channels) | client git consumption of upstream servers | alkgit |
| `tunnel` | `direct-tcpip`, `forwarded-tcpip`, `direct-streamlocal`, the `tcpip_forward`/`streamlocal_forward` listen flow | client `direct-tcpip` opens; the client `-R` leg (listen request + forwarded-channel acceptor) | alktunnels |
| `dynamic-forward` | SOCKS5-in-channel on session channels (family-native client shape) | client-side SOCKS consumption | alksocks |
| `tunnel` | `direct-tcpip`, `forwarded-tcpip`, `direct-streamlocal`, the `tcpip_forward`/`streamlocal_forward` listen flow | client `direct-tcpip` opens (which is what `-D` rides — see 1a) | alktunnels |
| `sftp` | *placeholder — declines until alkfs lands (OQ-13)* | client sftp half, also deferred | alkfs (future) |
**1a. No `dynamic-forward` feature.** Amended 2026-10-26 (review
untangle): the earlier graph carried a `dynamic-forward` feature
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: vanilla `ssh -D`
does its SOCKS5 handshake client-side and opens plain
`direct-tcpip` channels (POC-3 correction 2), and the family
behavior of `-D` is a **client-side local door** — alksocks' local
door given a `DialFn` backed by the alkssh client's
`direct-tcpip` opens, composed at the assembly. The server needs
only the `tunnel` row for the whole `-D` leg, 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.
2. **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. `git` does
not imply `session` (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); `dynamic-forward` does not imply
`session` (SOCKS-in-channel rides session channels but is gated at
dispatch, before any serve).
contract from POC-3 stage A). Note the `tunnel` client leg's SOCKS
door is *local to the dialing host* (alksocks composed at the
assembly), not a wire capability — the wire sees `direct-tcpip`
only.
3. **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
@@ -3,13 +3,22 @@ status: draft
last_updated: 2026-10-26
---
# ADR-006: Mapping decisions — route-1 bridge, exec-only git, SOCKS dispatch
# ADR-006: Mapping decisions — route-1 bridge, exec-only git, session dispatch
## Status
**Accepted** — 2026-10-26. All three decisions are
POC-3-validated against published sibling crates; amendments are new
ADRs.
ADRs. Amended same day (review untangle): point 3's **SOCKS-detect is
retired** — with the `dynamic-forward` feature removed (ADR-003 §1a),
session dispatch is parse-only and a request-less session channel
simply declines. The parse-vs-anything split was only ever needed if
a session channel could *be* a SOCKS stream; no consumer is that
client (vanilla `-D` rides `direct-tcpip`; the family `-D` door is
client-local, alksocks composed at the assembly). POC-3 stage B's
evidence survives as: (a) the load-bearing correction that vanilla
`-D` = client-side SOCKS + plain `direct-tcpip`, (b) proof that
SOCKS-in-channel is feasible if ever wanted back.
## Context
@@ -49,21 +58,18 @@ alkgit/alksocks/alktunnels/alktty).
fixups. Vanilla git behavior loses nothing — vanilla git never
speaks subsystem for git; the decline only refuses a manual
`ssh -s`-style invocation, with a reason.
3. **Session-channel dispatch: request-driven parse wins; detect
covers the request-less case.** The two POC-3 modes compose thus:
- a session channel that receives `exec_request` /
`pty_request` / `shell_request` takes the parse path first
(git parse under `git`; else pipe/shell under `session`);
- a session channel that receives **no request** (the
family-native SOCKS shape — a client speaking SOCKS5 in-band)
runs the **SOCKS-detect** under `dynamic-forward`: spawn, first-
byte sniff (`0x05`), bounded window — **500 ms default,
assembly-configurable**, silent or non-SOCKS → decline (logged);
- the detect never runs when the channel got a request, and the
parse never runs on a request-less channel. One channel, one
route, decided by what actually arrives.
This resolves OQ-02 (the composition was the unknown; the window
bound and configurability are now records, not questions).
3. **~~Session-channel dispatch: request-driven parse wins; detect
covers the request-less case~~ — amended: parse-only.** The
original decision composed the POC-3 modes as: requests take the
parse path (git under `git`, pipe/shell under `session`);
request-less session channels ran a SOCKS-detect
(`dynamic-forward`: spawn, first-byte `0x05` sniff, 500 ms
bounded window). With `dynamic-forward` removed from the feature
graph (ADR-003 §1a), the detect has no owner: **session dispatch
is parse-only** — requests parse to git (exec) or pipe/shell,
and a request-less session channel declines as unfeatured. This
still resolves OQ-02 (the composition was the unknown; the
resolution is now the simpler of the two candidates).
4. **The spawn-and-return rule is a structural constraint, not an
implementation detail.** russh 0.64.1-era servers run handler
callbacks inline inside the session's packet loop — a blocking
@@ -81,10 +87,10 @@ alkgit/alksocks/alktunnels/alktty).
recorded; the OQ list loses its highest-priority pending item; the
alkforge assembly gets an exact wiring recipe (POC-3 §C.4's
in-process fabric leg is the template, verbatim).
- **Negative**: the 500 ms detect window is a tunable that can be
misconfigured (too low: false declines on slow clients; too high:
slow-fail paths) — it is bounded and configurable by design, and
the decline is logged.
- **Negative**: the SOCKS-detect retirement removes a tunable the
POC proved workable — if a family client ever wants a remote SOCKS
door, the shape is re-addable via ADR (500 ms bounded window
preserved in the reference here).
- **Neutral**: route 2 remains documented in `mapping-table.md` as
the rejected alternative, with its rationale — useful if the
posture is ever revisited (that revisit is an ADR, not a code flip).
+3 -4
View File
@@ -25,12 +25,11 @@ alkcall trackers have their own OQ spaces).
## Theme B — session-channel dispatch & mapping details
### OQ-02: ~~What is the session-channel dispatch shape (SOCKS-detect vs parse, and who bounds the window)?~~ — resolved
### OQ-02: ~~What is the session-channel dispatch shape (SOCKS-detect vs parse, and who bounds the window)?~~ — resolved (then simplified)
- **Origin**: [poc-3-mapping-bakeoff.md](../research/poc-3-mapping-bakeoff.md) §Stage B.3, `mapping-table.md` §notes
- **Status**: resolved (ADR-006 point 3)
- **Resolution**: request-driven parse wins (exec/pty/shell → git-parse-else-pipe under their features); request-less session channels under `dynamic-forward` run the SOCKS-detect — spawn + first-byte sniff (`0x05`), bounded 500 ms default (assembly-configurable), silent/non-SOCKS → decline. One channel, one route, decided by what arrives. The composition — the actual unknown — was worked through against the POC-3 stage A/B constructions; the window bound and configurability record as values, not questions.
- **Cross-references**: ADR-006, ADR-003
- **Status**: resolved — resolved twice over. First by ADR-006 point 3 (dispatch composition: request-driven parse wins, request-less channels get the detect). Then **simplified by the ADR-003 §1a amendment**: the `dynamic-forward` feature retired (no consumer is ever the family-native SOCKS-in-channel client; vanilla `-D` is client-side SOCKS + plain `direct-tcpip`; the family `-D` door is alksocks composed locally at the assembly), so the detect lost its owner. Final shape: **session dispatch is parse-only**; request-less session channels decline. POC-3 stage B survives as the feasibility record for re-adding a remote SOCKS door via explicit ADR.
- **Cross-references**: ADR-006, ADR-003 §1a
### OQ-03: ~~Username disposition~~ — resolved
+8 -7
View File
@@ -39,7 +39,7 @@ request-level extras.
| `session` → `pty_request`+`shell_request` | `ssh host` interactive shell | `session` | alktty producer: `NegotiateRequest` constructed locally → `drive_session` (or pre-allocated variant) | `pty_request` args → `TerminalParamsWire` (term/cols/rows/pixels; SSH modes → `modes` JSON reserved); shell case needs a cmd convention (OQ); PTY mode merges stderr into stdout (kernel PTY). **[POC-3 corrected]** the data-plane mapping is a *translation bridge*, not a byte pump: alktty's plane is chunk-framed both directions (stdin/stdout/stderr + CTRL_IN/CTRL_OUT chunks), so alkssh encodes SSH data→stdin chunks and decodes stdout→data, stderr→extended_data, exit chunk→exit-status/exit-signal. New OQ: the exit chunk carries no signal identity (negative code only) |
| `session` → `exec_request` (non-git command) | `ssh host "cmd"` | `session` | alktty pipe mode: `terminal: None`, separate stderr | stderr flows back as SSH `extended_data` code 1 — only valid without PTY |
| `session` → `exec_request`/`subsystem_request` git | `git push/clone ssh://` | `git` | alkgit: parse command → `Service` + repo (same grammar as `preamble.rs`, without `\0host=`) → `serve_duplex_resolved(repo_id, identity, service, duplex, ...)` | Process-mode dialect is correct (SSH supplies service+repo out of band; server speaks first = advertisement). **[POC-3 corrected]** git over SSH is exec-only: a `subsystem` request carries the service *name* but no repo id, so even a git-service subsystem must decline — vanilla git always executes `git-upload-pack '/repo'`. POC-3: vanilla clone/push/second-push through the published seam + substrate composition, exit-status in both directions |
| `session` → SOCKS5-in-channel | `ssh -D` | `dynamic-forward` | alksocks: `serve_socks5_over(channel_duplex, dial, egress, accept, can_bind)` | alkssh embeds `Socks5Producer`-shaped config from the assembly; `DialFn`/`EgressFactory` injected at assembly. **[POC-3 corrected]** this row is the family-native client shape — a vanilla `ssh -D` does its SOCKS5 client-side and opens plain `direct-tcpip` channels (the `tunnel` row above), so the two rows split by client shape, not just feature. POC-3: SOCKS-detect (bounded first-byte sniff) + real curl through the session channel |
| `session` → SOCKS5-in-channel | (n/a — retired) | ~~`dynamic-forward`~~ **retired (ADR-003 §1a)** | was alksocks: `serve_socks5_over` | **Post-architecture retirement:** no consumer is ever the family-native client shape this row served. Vanilla `ssh -D` does SOCKS client-side and opens plain `direct-tcpip` (POC-3 correction 2 → the `tunnel` row); the family `-D` behavior is a *client-local* alksocks door over the alkssh client's `direct-tcpip` opens (assembly composition). POC-3 stage B remains the evidence that SOCKS-in-channel is feasible if a family client ever wants a remote SOCKS door (back via explicit ADR). **[ADR-003 amended]** |
| `direct-tcpip` | `ssh -L` | `tunnel` | alktunnels (two routes, below) | Target = `SubstrateAddr::HostPort`/`Path`; `Substrate` = Tcp/Unix |
| `forwarded-tcpip` | `ssh -R` (data channel) | `tunnel` | alktunnels: `register_tunnel_forwarded_openable`'s `ForwardedHandler(params, BiStream)` shape — in the adapter, hand the russh channel duplex to the handler | listen flow via global-request rows below |
| `direct-streamlocal` | unix-socket forward | `tunnel` | same `SubstrateAddr::Path` treatment | unix-only; decline on other platforms (compile-time feature + runtime decline) |
@@ -60,12 +60,13 @@ a shell, a pipe-mode command. The failure matrix this produces:
unfeatured channel type declines at open.
Dispatch is parse-then-route by command shape: parsing to a git
service and repo requires `git`; SOCKS-in-channel requires
`dynamic-forward`; otherwise the alktty pipe/pty path requires
`session`. Parsing happens before dispatch and before any resource
allocation (a leaked channel alloc is a capability leak — decline
first). Exact grammar/precedence is a phase 1 spec question (OQ
candidate).
service and repo requires `git`; otherwise the alktty pipe/pty path
requires `session`. Parsing happens before dispatch and before any
resource allocation (a leaked channel alloc is a capability leak —
decline first). (Post-architecture: the former SOCKS-detect dispatch
arm is retired with the `dynamic-forward` feature — ADR-003 §1a /
ADR-006 amendment; request-less session channels decline.) Grammar/
precedence per ADR-006.
## Global-request rows (`tcpip_forward` / `streamlocal_forward`) — the `-R` listen flow