Files
alkssh/tasks/implementation/w3-tty-session.md
T
glm-5.3-flash d1f755f2d9 docs(tasks): decompose wave 3 — the server mappings
Six implementation tasks + the gate under tasks/implementation/,
chained sequentially (the mappings replace stubs in the same Handler
impl and share the decline-matrix test binaries):

- w3-seam-config: the serve-config's per-feature seam handles
  (alktty backends/ownership/selector; the alkgit quartet/pipeline/
  push-options flag) + the optional sibling deps — wave 3's first
  course correction to the wave-1 config surface
- w3-decline-tracing: the wave-2 gate's booked finding — the
  decline/serve-path tracing vocabulary, landed before the mappings
- w3-git-mapping: the full exec grammar replaces the wave-2 probe +
  the serve_duplex_resolved dispatch + the store-and-take handoff
- w3-tty-session: the translation bridge + the exit-derivation
  contract (rules 1-6, the signum table)
- w3-tunnel-direct: the route-1 bridge (accept only after the fabric
  open succeeds) + set_identity propagation + the capacity-across-
  fabric-window mechanics
- w3-tunnel-listen: the -R listen flow + the forwarded-tcpip data
  leg (the POC-untested leg; the stop-and-book provision)
- review-wave-3: the gate

The plan carries the wave-3 decomposition record with four
interpretations flagged for the gate (the serve-config course
correction, the already-discharged server-side sftp bullet, the
w3-tunnel-listen open mechanics, the booked-items' wave-3 homes).
taskgraph validate: 26 tasks, clean.

Sibling pins verified against crates.io at decomposition time:
alktty 0.8.1 / alkgit 0.1.0 / alktunnels 0.4.1 / alkcall 0.10.1.
2026-10-28 12:32:13 +00:00

7.7 KiB
Raw Blame History

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
w3-tty-session Wave 3 — session → alktty (translation bridge + exit derivation) pending
w3-git-mapping
moderate medium phase implementation

Description

Land the session branch — the biggest single-row surface of the spine (mapping.md's session rows): pty/shell/pipe-exec become a real serving path through the published alktty seam, as a translation bridge, not a byte pump (POC-3 §D.1). Everything POC-3 stage D proved, re-landed as contract:

  • The per-channel state + the store-and-take reuse — the channel store w3-git-mapping lands (channels.remove(&id) → into_stream() in the spawned serving task) gains the session rows' per-channel state: the PTY params a pty_request delivered (consumed by the later shell_request/exec_request on the same channel — ssh -t host "cmd" sends pty-then-exec and must serve PTY-mode; a shell_request without a preceding pty serves pipe-mode tty = None — decide whether such a shell serves or declines and record).
  • Negotiation, built locally, never wire-derived (OQ-08 — the serve-config is the whole truth): pty_request args → TerminalParamsWire{term, cols, rows, pixel_w/h, modes} (SSH ttymodes → the reserved modes JSON — alktty OQ-44's reservation, deferred there); shell_request → NegotiateRequest{carriage: "raw", cmd: shell_argv (the serve-config shell, verbatim), cwd: session_cwd, env: session_env, backend: the seam selector} → drive_session_pre_negotiated(writer, reader, req, backends, ownership, identity) spawned; exec_request (pipe row) → cmd = wrapper ++ [cmd] (the OQ-08 argv append — vanilla quoting semantics; never naively split), tty = None pipe mode. Unset-declines (OQ-08): shell_argv unset → shell_request declines; exec_wrapper_argv unset → the pipe row declines — never a fallback. Decide the wire shape per row (capability decline — channel stays usable — vs the serving-path failure+close shape) and record.
  • The translation bridge — two small adapters around a duplex the serving task drives (the POC-3 §D.1 shape): SSH data/EOF → Chunk::stdin (decide the EOF translation — the zero-length stdin-chunk sentinel vs alktty's ControlMessage::Eof CTRL_IN — verify what alktty's ChunkReader/backend actually consumes and record); stdout chunks → SSH data; stderr chunks → extended_data(1) (PTY mode merges upstream — the backend's stderr stream is None there, so the translation is universal, no mode-switch); the exit chunk (STREAM_CTRL_OUT) → the derivation contract. No unbounded buffering in either adapter (convention 9).
  • Control rows — window_change_request → ControlMessage:: Resize CTRL_IN; signal → ControlMessage::Signal (alktty's uppercase-name vocabulary; name accepted verbatim — decide the decline shape for names alktty's backend semantics reject and record). Mechanics: the requests arrive on the packet loop after the serving task owns the wire — reach them into the spawned bridge through a per-channel control surface (bounded mpsc — decide + record; a silent unbounded proxy is a bounded-resources bug).
  • The exit derivation contract (mapping.md rules 1–6, normative): the alktty exit chunk ({"type":"exit","code":i32}, exit-chunk-is-last) is the sole fidelity input, never widened: code ≥ 0 → exit-status(N); negative −N, N ≥ 2 → exit-signal(name) via the server-side Unix signum→name table (crate-local; not derived from alktty's signal_from_name inverse unless that is public and sound — decide the table's form and record; core_dumped never asserted, the bool stays false); −1 → exit-status(255), never a signal claim (rule 3 — the SIGHUP collision accepted symmetrically, per the mapping.md rationale); non-Unix adds no other negatives (rule 4); the PTY-path rule-5 fidelity limit documented in the module docs (signal- terminated PTY sessions arrive exit-status(1) — portable-pty's exit surface, upstream); unmapped negatives → 255 (rule 6). Sent before close, from the spawned task.
  • env_request stays declined (OQ-09 — unchanged from wave 2); session_cwd/session_env ride NegotiateRequest only.
  • Contract rows through the harness, per build — the wave-2 stub rows (pty_row_stub/shell_row_stub/ pipe_row_stub) replaced; the decline matrix's unset-field rows — feature on + handle unset → the rows decline; featured+set serve through the alkssh-side TtyBackend shim double (alktty's MockBackend/testing.rs are pub(crate) + cfg(test) — never importable; alkssh hand-rolls the shim: a backend that records TtyParams, emits scripted stdout/stderr/exit chunks and consumes stdin/resizes/signals — per the TtyHandle field shapes). The identity-landing item (the parked wave-2 note) asserted through the row that observes the identity at drive_session_pre_negotiated.
  • The real-tool smoke — one OpenSSH pty session through the harness against the shim (or against a real PTY via a dev-dep alktty local composition — decide which posture and record; the real bash/stty size fidelity was POC stage D's, wave 5 re-lands it as e2e): the smoke owns the wire rows a plain rustsh-client row can't pin (echo paths, stty size-grade fidelity are wave-5's matrix; here the pty lifecycle rows only).

Acceptance Criteria

  • pty/shell/pipe-exec rows serve through drive_session_pre_negotiated (spawned, handler fast); the store-and-take + per-channel PTY state reused from the git task's handoff; pipe-less shell decided + recorded
  • pty_request → TerminalParamsWire (ttymodes → the reserved JSON), shell_request → shell argv, exec → wrapper ++ [cmd], cwd/env session-side; unset-declines on both OQ-08 rows with recorded wire shapes
  • The bridge translated per row (stdin chunks + EOF shape verified against alktty; stdout→data; stderr→extended_data(1); resize/signal CTRL_IN reach the serving path through a bounded per-channel surface)
  • The derivation contract rules 1–6 in code with the signum→name table; −1→255; core_dumped never; PTY-path rule-5 documented; exit-status before close from the spawned task — pinned by rows
  • The decline matrix per build green through the harness (unset- field rows included); identity ride asserted; the env_request decline unchanged; skeleton stub rows migrated
  • The real-tool pty smoke row (posture decided + recorded)
  • cargo test (+ per-feature + --all-features rows), clippy ×2 (-D warnings), fmt, cargo doc --no-deps pass

References

  • docs/architecture/mapping.md (the session channel-type row, request-level rows, §Exit chunk → SSH exit reporting — rules 1–6)
  • docs/architecture/decisions/006-mapping-bridge-and-dispatch.md (decision 4 — spawn-and-return: status before close from the spawned task), docs/architecture/decisions/003-feature-graph.md
  • docs/architecture/auth.md §Server direction step 4 (the identity ride), §Dispositions (OQ-03)
  • docs/research/poc-3-mapping-bakeoff.md §Stage D (the bridge shape, control rows, the signal/exit fidelity findings, §D.3 mechanics)
  • docs/research/reverse-runner.md (pipe-mode pid-only signal caveat — the fidelity note)
  • alktty 0.8.1: adapter.rs:234-241 (drive_session_pre_negotiated), negotiation.rs (NegotiateRequest/TerminalParamsWire), wire.rs (chunks, readers/writers), control.rs (ControlMessage), backend.rs (TtyHandle/TtyParams/TtyBackend shapes)

Notes

To be filled at implementation time.