From 9217a8b3cf6c4f2f196b582d10f148d1954940f9 Mon Sep 17 00:00:00 2001 From: "glm-5.3-flash" Date: Tue, 27 Oct 2026 14:08:14 +0000 Subject: [PATCH] =?UTF-8?q?docs(architecture):=20OQ-10=20premise=20correct?= =?UTF-8?q?ed=20=E2=80=94=20the=20negative=20exit=20code=20IS=20the=20sign?= =?UTF-8?q?al=20identity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OQ-10's seed overstated alktty's exit-chunk design as a 'signal-identity gap': the negative code is the documented signal encoding (alktty ADR-004, ported from alknet ADR-055; src/local/pipe.rs::exit_code_from — Unix ExitStatus::signal() → -sig), a tty/pty-grounded convention - 'alktty widens the chunk' demoted from co-equal candidate to gated exception: it would widen ControlMessage::Exit's one-way parsed shape, the backend-wide exit_code API, and the alknet ADR-055 lineage — requires an alktty ADR with explicit tty/pty-grounded justification, never an alkssh convenience ask - OQ-10 re-scoped to the real remaining detail: −1 (wait failure vs SIGHUP) collision, core-dumped disposition, non-Unix semantics, name derivation - correction notes added in place: poc-3 §D.2, pocs.md, mapping-table.md (both rows); mapping.md exit_signal row updated Verification: cargo doc --no-deps (docs-only change) --- docs/architecture/mapping.md | 6 ++---- docs/architecture/open-questions.md | 22 ++++++++++++++++------ docs/research/mapping-table.md | 4 ++-- docs/research/poc-3-mapping-bakeoff.md | 18 ++++++++++++++++++ docs/research/pocs.md | 6 +++++- 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/docs/architecture/mapping.md b/docs/architecture/mapping.md index d4025d4..66fea07 100644 --- a/docs/architecture/mapping.md +++ b/docs/architecture/mapping.md @@ -76,7 +76,7 @@ the served listen flow is implementation-phase contract-test territory.* | `signal` | signal control (alktty names verbatim); PTY mode reaches the process group, pipe mode is pid-only (backend semantics, recorded in OQ-10) | | data / `extended_data(1)` | the translation bridge (rows table) | | `exit_status` | from the alktty exit chunk — sent **before** close, from the spawned task via `Session::handle()` (POC-3 §D: proven both legs) | -| `exit_signal` | only for signal-terminated exits; OQ-10 (chunk carries no signal name — alktty ask + assembly fallback pending) | +| `exit_signal` | only for signal-terminated exits; derived from the exit chunk's negative code — `−N` = terminated by signal N on Unix (alktty ADR-004's documented convention). Details pending OQ-10: the `−1` wait-failure/SIGHUP collision, core-dumped (discarded by alktty; likely never asserted in v1), non-Unix shift | ## The structural constraint: spawn-and-return @@ -123,9 +123,7 @@ under `--all-features`. - **OQ-08** (shell convention — status: open): blocks this doc's `shell_request`/`NegotiateRequest.cmd` row detail. -- **OQ-10** (exit-chunk signal identity — status: open): blocks the - `exit_signal` row's precise encoding; the fallback direction is - recorded there. +- **OQ-10** (exit-signal derivation details — status: open, **premise corrected**): the negative code *is* the signal identity by alktty's documented convention, so no chunk widening is justified by default (the upstream option's gate and consequence analysis are recorded there). This blocks only the `exit_signal` row's derivation detail — the `−1` disposition, core-dumped assertion, non-Unix note. - **OQ-11** (streamlocal platform decline shape — status: open). - ~~OQ-02~~ (dispatch composition — resolved by ADR-006, as amended). diff --git a/docs/architecture/open-questions.md b/docs/architecture/open-questions.md index a4d61a1..01d4a07 100644 --- a/docs/architecture/open-questions.md +++ b/docs/architecture/open-questions.md @@ -94,14 +94,24 @@ alkcall trackers have their own OQ spaces). - **Blocked on**: a concrete consumer use case requiring wire-supplied environment variables. - **Resolution**: Not yet decidable — no use case; decline stands. -### OQ-10: The alktty exit chunk carries no signal identity — widen the chunk or map by assembly policy? +### OQ-10: ~~"The alktty exit chunk carries no signal identity — widen the chunk or map by assembly policy"~~ — premise corrected; re-scoped -- **Origin**: [poc-3-mapping-bakeoff.md](../research/poc-3-mapping-bakeoff.md) §D.2, `mapping-table.md` (exit_signal row) -- **Status**: open +- **Origin**: [poc-3-mapping-bakeoff.md](../research/poc-3-mapping-bakeoff.md) §D.2, `mapping-table.md` (exit_signal row). **Premise corrected 2026-10-27**: the POC's "signal-identity gap" framing was an incorrect assumption, surfaced during review; the incorrect assumption is documented here, not silently edited. +- **Status**: open — re-scoped to the SSH-fidelity derivation details; the detail decision lands during `mapping.md` - **Priority**: medium -- **Impacts**: blocks the `exit-signal` fidelity detail in `mapping.md`; an in-family upstream ask (alktty wire change, third-party-upstream posture applies family-internally too). -- **Resolution**: pending. Candidates (per POC-3): alktty widens the exit chunk with the signal name (filed as an ask — alkssh never silently works around), or alkssh maps negative exit codes via assembly policy (fidelity compromise: name inferred/coarse). Also in scope for the same OQ: pipe-mode signals are pid-only vs PTY process-group scope (backend-semantics fidelity note for the session rows). Decide the *fallback* during `mapping.md`; the upstream ask stays open until alktty answers. -- **Cross-references**: OQ-08 +- **The incorrect assumption, explicitly**: the OQ seed treated the exit chunk's lack of a signal *name* as a design gap, with "alktty widens the chunk" as a co-equal candidate casually offered ("filed as an ask"). Both halves were wrong. + - **The chunk does carry signal identity.** The negative exit code *is* the signal encoding, by a deliberate documented one-way convention (alktty ADR-004 decision 1, ported from alknet ADR-055; implemented in alktty `src/local/pipe.rs::exit_code_from` — on Unix, `ExitStatus::signal() → -sig`). The rationale is tty/pty-ground: the backend reaps a Unix wait-status, and the negative-code encoding preserves exactly that ground truth across every backend behind a single `exit_code → i32` API. The OQ's claim "carries no signal identity" overstated an *information-preserving* encoding as an absence. + - **Upstream widening is not a justified candidate by default.** It is not a wire-only tweak: widening `ControlMessage::Exit`'s shape is a one-way-surface change every ctrl-out consumer parses; the backend API (`TtyHandle.exit_code → i32`, shared by every backend) would have to widen with it — changing how every backend reaps and reports; and the negative-code convention is shared with alknet's docker-carriage lineage (alknet ADR-055). "It would be easier for alkssh" is not a tty/pty-grounded justification; alkssh cannot shift alktty's contract for its own fidelity convenience, and will not silently work around the wire either. +- **The tty/pty conventions alkssh consumes, never repurposes**: the exit control chunk (JSON, never zero-length, "exit chunk is last" invariant) and the data-plane sentinels (zero-length stdin = client EOF — the form alkssh's SSH-EOF translation uses; zero-length stdout = stdout-drained). Both have tty/pty rationale in alktty's ADR-004/`tty-wire.md`; they are upstream decisions, not alkssh levers. +- **What is genuinely missing** (the re-scoped open detail — all alkssh mapping-contract territory): + 1. **Name derivation**: SSH `exit-signal` requires a name; `−N → name` is alkssh server-side work (a Unix signal table — signal semantics belong to the server's platform, where the backend reaps). + 2. **Core-dumped**: alktty discards `ExitStatus::core_dumped()`; SSH's `exit-signal` carries a bool. Currently unrecoverable — disposition decided during `mapping.md` (likely direction: never asserted, v1). + 3. **The `−1` collision** (pre-existing, documented in alktty ADR-004's consequences): `−1` is the backend wait-failure sentinel, colliding with a real SIGHUP termination. The SSH mapping must pick a disposition (`exit-status` with an opaque value, no exit request, or a diagnostic band). alktty has an *additive* escape (`exit_error` message type) recorded in its own ADR — an upstream improvement, not an alkssh ask by default. + 4. **Non-Unix platforms**: the negative-code convention is `#[cfg(unix)]`; Windows backends report `code()` only (signal termination essentially does not occur) — the mapping detail records the graceful shift. + 5. **Pipe-mode pid-only vs PTY process-group signal scope** (carried from the POC): backend semantics, not a mapping change — a fidelity note on the `signal` row. +- **Impacts**: blocks the `exit-signal` fidelity detail in `mapping.md` only; no upstream ask stands by default. +- **Resolution**: pending. Direction fixed: alkssh derives exit-signal fidelity from the existing wire (the documented negative-code convention). Decide the derivation details (−1 disposition, core-dumped assertion, non-Unix note) when writing `mapping.md`; record them as an ADR-006 amendment or a dedicated ADR. Any *upstream* widening of the exit chunk would require its own alktty ADR with the full consequence analysis above of the tty/pty rationale — that gate is stated here so it cannot be skipped. +- **Cross-references**: OQ-08, [mapping.md](mapping.md) (`exit_signal`, `signal` rows), alktty ADR-004 / `tty-wire.md` (alktty repo), alknet ADR-055 ### OQ-11: `direct-streamlocal` platform surface — compile-time or runtime decline shape? diff --git a/docs/research/mapping-table.md b/docs/research/mapping-table.md index ebe0e84..862f4f2 100644 --- a/docs/research/mapping-table.md +++ b/docs/research/mapping-table.md @@ -36,7 +36,7 @@ request-level extras. | SSH channel type | Vanilla use | Feature (candidate) | Family seam | Notes | |---|---|---|---|---| -| `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` → `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). **[Corrected 2026-10-27]** the negative code *is* the signal encoding (alktty ADR-004); the OQ is about server-side derivation details, not chunk widening | `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 | (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]** | @@ -91,7 +91,7 @@ spec detail deferred to phase 1. | `signal` | `TtyControlHandle::signal(name)` (alktty's `signal_from_name` accepts the HUP/INT/TERM/... names verbatim) | | data / `extended_data(1)` | channel duplex in/out; stderr only meaningful in pipe mode (PTY merges) | | `exit_status` | from `TtyHandle.exit_code` (`TtySession.wait()` semantics: i32; negative = signal-terminated) — alkssh sends the SSH `exit-status` request before channel close (POC-3: proven both directions via the cloned `Session::handle()` from the spawned serving task) | -| `exit_signal` | Only reachable in signal-termination cases; mapped from the same exit-code path (OQ: exact encoding). **[POC-3]** the alktty exit chunk is `{"type":"exit","code":N}` — a negative code implies signal termination but carries no *name*, so the faithful `exit-signal` mapping needs alktty to widen the chunk or an assembly-policy default | +| `exit_signal` | Only reachable in signal-termination cases; mapped from the same exit-code path (OQ: exact encoding). **[POC-3]** the alktty exit chunk is `{"type":"exit","code":N}` — a negative code implies signal termination but carries no *name*, so the faithful `exit-signal` mapping needs alktty to widen the chunk or an assembly-policy default. **[Corrected 2026-10-27]** the "needs widening" premise was wrong: the negative code *is* the documented signal encoding (alktty ADR-004, ported from alknet ADR-055; `pipe.rs::exit_code_from` — Unix `ExitStatus::signal() → -sig`), so the name is derivable server-side by alkssh; no chunk change is justified by default. The genuine gaps (core-dumped discarded, the `−1` wait-failure/SIGHUP collision, non-Unix) are alkssh mapping-contract details — alkssh OQ-10 | ## Data-plane / limits contract diff --git a/docs/research/poc-3-mapping-bakeoff.md b/docs/research/poc-3-mapping-bakeoff.md index df434bd..e9952d8 100644 --- a/docs/research/poc-3-mapping-bakeoff.md +++ b/docs/research/poc-3-mapping-bakeoff.md @@ -280,6 +280,24 @@ exit chunk until the whole group dies); PTY-mode signals reach the process group (correct terminal semantics). The signal row's fidelity therefore depends on the tty/pipe mode split the table already notes. +**[Corrected 2026-10-27]** The "signal-identity gap" framing above was +an incorrect assumption, surfaced during review and now corrected in +the alkssh tracker (OQ-10). The exit chunk's negative code *is* the +signal identity, by alktty's documented one-way convention (alktty +ADR-004 decision 1, ported from alknet ADR-055; +`src/local/pipe.rs::exit_code_from` maps Unix `ExitStatus::signal()` +to `-sig`) — the tty/pty ground truth (a reaped wait-status, +preserved across one backend-wide `exit_code → i32` API), not an +absence. The signal *name* is derivable server-side by alkssh, so +"alktty widens the chunk" is not a justified default candidate: it +would change `ControlMessage::Exit`'s one-way parsed shape, force the +backend-wide API to widen, and ripple the alknet ADR-055 lineage — an +alktty ADR with explicit justification, only if a fidelity failure +demands it. The genuine gaps — core-dumped (discarded), the `−1` +wait-failure/SIGHUP collision (pre-existing, documented in alktty +ADR-004 with an additive `exit_error` escape), and non-Unix semantics +— are alkssh mapping-contract details. + ### 3. Client-visible mechanics (russh 0.64 API facts from this stage) - russh client has `Channel::exec()` (there is no `request_exec`); diff --git a/docs/research/pocs.md b/docs/research/pocs.md index d96c559..4251afc 100644 --- a/docs/research/pocs.md +++ b/docs/research/pocs.md @@ -139,7 +139,11 @@ forms). russh server loopback + vanilla clients; per stage: the load-bearing session-row corrections: the alktty bridge is a **translation bridge** (chunk framing both directions, not a byte pump) and the alktty exit chunk carries no signal identity - (`poc-3-mapping-bakeoff.md` §Stage D). + (`poc-3-mapping-bakeoff.md` §Stage D). [Corrected 2026-10-27: the + "no signal identity" claim overstated the gap — the exit chunk's + negative code *is* the documented signal encoding (alktty ADR-004); + see `poc-3-mapping-bakeoff.md` §D.2's correction and alkssh + OQ-10.] **Validates:** the one-seam-per-mapping hypothesis; the parse-then- route dispatch; stderr semantics in pipe vs PTY mode; EOF/close/signal