From e9f78bf6f72e50bda319ba3353e742806d45d60c Mon Sep 17 00:00:00 2001 From: "glm-5.3-flash" Date: Mon, 26 Oct 2026 15:12:52 +0000 Subject: [PATCH] docs(architecture): ADR-006 (route-1 bridge, exec-only git, SOCKS dispatch) + OQ-02 resolved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ADR-006 (Accepted): direct-tcpip rides the adapter-side bridge (alktunnels route 1; registry-side bridging rejected as door-posture inversion; absence note resolved); git over SSH is exec-only with subsystem declining (vanilla git's connection model is exec of git-{upload,receive}-pack — POC-3 grammar table); session dispatch: request-driven parse wins, request-less channels get the bounded SOCKS-detect (first-byte 0x05, 500 ms default, assembly-configurable); spawn-and-return recorded as a structural constraint - OQ-02 resolved by ADR-006; README doc/ADr tables + OQ summary updated --- docs/architecture/README.md | 4 +- .../006-mapping-bridge-and-dispatch.md | 100 ++++++++++++++++++ docs/architecture/open-questions.md | 12 +-- 3 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 docs/architecture/decisions/006-mapping-bridge-and-dispatch.md diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 475b83b..07272b0 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -40,11 +40,13 @@ below are being written. No implementation until specs reach | [ADR-003](decisions/003-feature-graph.md) | The capability feature graph — flat, independent, two-direction | Accepted | | [ADR-004](decisions/004-auth-dispositions.md) | Auth dispositions — publickey-only, fail-closed, fingerprint identity | Accepted | | [ADR-005](decisions/005-at-rest-key-custody.md) | At-rest key custody — vault-wrapped OpenSSH-PEM, decrypt-at-use | Accepted | +| [ADR-006](decisions/006-mapping-bridge-and-dispatch.md) | Mapping decisions — route-1 bridge, exec-only git, SOCKS dispatch | Accepted | ## Open Questions Tracked in [open-questions.md](open-questions.md) — OQ-01 … OQ-13, -seeded from the phase 0 POC findings and research docs. +seeded from the phase 0 POC findings and research docs. Five resolved +(ADR-002/006 review + evidence), four deferred with named blockers. ## Document Lifecycle diff --git a/docs/architecture/decisions/006-mapping-bridge-and-dispatch.md b/docs/architecture/decisions/006-mapping-bridge-and-dispatch.md new file mode 100644 index 0000000..2caf170 --- /dev/null +++ b/docs/architecture/decisions/006-mapping-bridge-and-dispatch.md @@ -0,0 +1,100 @@ +--- +status: draft +last_updated: 2026-10-26 +--- + +# ADR-006: Mapping decisions — route-1 bridge, exec-only git, SOCKS dispatch + +## Status + +**Accepted** — 2026-10-26. All three decisions are +POC-3-validated against published sibling crates; amendments are new +ADRs. + +## Context + +`mapping-table.md` left three choices explicit for the architecture +pass: which of alktunnels' two routes serves `direct-tcpip`; whether +git over SSH has a subsystem path; and the session-channel dispatch +shape when one channel type serves both parseable commands and +SOCK5-in-channel traffic. POC-3 resolved all three by construction +(real git 2.43, real curl 8.5, real OpenSSH through published +alkgit/alksocks/alktunnels/alktty). + +## Decision + +1. **`direct-tcpip` rides the adapter-side bridge (alktunnels route + 1).** alkssh holds a `ChannelClient` into the family fabric and + calls `TunnelSession::open_direct(client, target, substrate)`, + pumping `take_halves()` against the russh channel + (`channel_open_direct_tcpip` → accept only after the fabric open + succeeds → `copy bidirectional`). POC-3 stage C proved it end to + end (8 MiB in ~333 ms; fabric-open failure maps to an explicit + SSH `ConnectFailed` reject — accept happens only post-success, + since `accept()` consumes the handle). Registry-side bridging + (route 2: alkssh-shaped `ChannelOperations` in a serving + endpoint's registry) is **rejected**: it inverts the door posture + by putting adapter work upstream of the door. The alktunnels + *absence* note (no serve-over-stream entry) is thereby resolved — + route 1 never needed one. +2. **Git over SSH is exec-only; every subsystem request declines.** + The vanilla git connection model is: git spawns its ssh transport, + which opens one session channel and sends + `exec_request "git-{upload,receive}-pack '/repo'"` — the repo path + rides inside the command string, so a subsystem request (name + only, no repo id) can never dispatch. POC-3 proved the parse + grammar (quoted/unquoted paths, service-only → decline, + non-git → parse-then-fail with no resource alloc) and + clone/push/second-push through the published seam with zero + 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). +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 + handler deadlocks against the peer's follow-up bytes (POC-3 + proved by construction: receive-pack pushes bytes immediately + after the exec request). Every serving path is + `tokio::spawn` + handler-returns-fast; exit reporting from the + spawned task rides the cloned `Session::handle()` — + `exit-status`(then `close`) before channel close, `exit-signal` + for signal termination (signal-name fidelity: OQ-10). + +## Consequences + +- **Positive**: every one-seam-per-mapping row now has its route + 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. +- **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). + +## References + +- `docs/research/poc-3-mapping-bakeoff.md` (stages A–C: all three + decisions' evidence; §C.4 the fabric wiring recipe) +- `mapping-table.md` (the rows + the absence note this ADR resolves) +- ADR-003 (the feature gates these dispatch decisions sit behind) +- `docs/research/reverse-runner.md` (the client-direction mirror of + leg 1 — the `-L`/`-R` legs the same route-1 bridge serves) +- OQ-02 (resolved by this ADR), OQ-10 (exit-signal fidelity, open) \ No newline at end of file diff --git a/docs/architecture/open-questions.md b/docs/architecture/open-questions.md index 02d272b..aa33b54 100644 --- a/docs/architecture/open-questions.md +++ b/docs/architecture/open-questions.md @@ -25,16 +25,12 @@ 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)? +### OQ-02: ~~What is the session-channel dispatch shape (SOCKS-detect vs parse, and who bounds the window)?~~ — resolved - **Origin**: [poc-3-mapping-bakeoff.md](../research/poc-3-mapping-bakeoff.md) §Stage B.3, `mapping-table.md` §notes -- **Status**: deferred(unclear) -- **Door type**: two-way -- **Priority**: high -- **Impacts**: blocks the exact dispatch contract detail in the `mapping.md` spec (ordering: exec-parse vs SOCKS-detect vs pipe; the detect window's bound; crate default vs assembly config). -- **Investigation**: the pieces exist — POC-3 proven both modes (stage A parse-then-route for git; stage B bounded first-byte sniff for SOCKS) and proven both serve "session" channels behind different features. Unclear is the composition: walk 2+ concrete session-channel flows (git exec, family-native SOCKS client, plain pipe exec) and pin dispatch precedence, the detect window's default bound, and the configuration seam. Do during `mapping.md` writing. -- **Resolution**: Not yet decidable by inspection — needs the composition worked through, not new facts. -- **Cross-references**: OQ-08 +- **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 ### OQ-03: ~~Username disposition~~ — resolved