docs(architecture): ADR-006 (route-1 bridge, exec-only git, SOCKS dispatch) + OQ-02 resolved

- 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
This commit is contained in:
glm-5.3-flash committed 2026-10-26 15:12:52 +00:00
1 parent 4d4da6651c
commit e9f78bf6f7
3 files changed
+107 -9

No files matched your search

+3 -1
View File
@@ -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
@@ -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)
+4 -8
View File
@@ -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