Files
alkssh/docs/architecture/decisions/003-feature-graph.md
T
glm-5.3-flash 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.
2026-10-28 05:08:01 +00:00

6.6 KiB
Raw Blame History

status, last_updated
status last_updated
reviewed 2026-10-28

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

  1. The feature set (cargo features on alkssh, all off by default):

    Feature Serves (inbound) Consumes (outbound) Family seam
    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 (the streamlocal rows — channel and listen — are runtime-Unix-gated, OQ-11) client direct-tcpip opens (which is what -D rides — see 1a) alktunnels
    sftp placeholder — declines until alkfs lands (OQ-13 resolved) client sftp half, same placeholder 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 consumer ever genuinely wants a remote SOCKS door (the OQ-01 relay/browser leg — resolved out of scope: no such consumer exists today), 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). 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 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.

  4. Client-side fail-closed at the acceptor. Server-initiated channel opens (forwarded-tcpip is the real one — the -R data 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).

  5. Never features. x11 and auth-agent forwarding 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.

  6. Honest advertisement per build. What the wire advertises mirrors what features are on: auth methods is 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 session makes 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-features runs 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 (resolved — the placeholder contract stands), OQ-02 (resolved by ADR-006)