From f36c2686944842719643a69f9569586008a963e6 Mon Sep 17 00:00:00 2001 From: "glm-5.3-flash" Date: Tue, 27 Oct 2026 15:19:08 +0000 Subject: [PATCH] =?UTF-8?q?docs(architecture):=20OQ-10=20reopened=20?= =?UTF-8?q?=E2=80=94=20premise=20corrected=20in=20place,=20derivation=20de?= =?UTF-8?q?tails=20deferred=20to=20mapping.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OQ-10 is not resolved: the mapping-contract derivation details (name table, core-dumped disposition, −1 disposition) are working dispositions on mapping.md's exit rows until formalized there / as an ADR-006 amendment. The 1a40bfb commit prematurely closed them and left ADR-006's reference (OQ-10 open) stale. - Premise correction integrated into the seed, not argued after the fact; the portable-pty 0.9.0 source now cited as verified (From maps signal-terminated → code 1 + locale-dependent strsignal string; the number is unrecoverable). - mapping.md: exit_signal row and the Open Questions entry updated to open OQ-10 with the working-draft framing. Verification: cargo test (2/2 targets ok), cargo clippy --all-targets -- -D warnings, cargo fmt --check (docs-only change) --- docs/architecture/mapping.md | 4 ++-- docs/architecture/open-questions.md | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/architecture/mapping.md b/docs/architecture/mapping.md index b96a91f..3b77c50 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). `−1` (alktty's could-not-determine sentinel — alktty ADR-004 §4) → `exit-status(255)`, never a signal claim | -| `exit_signal` | signal-terminated exits on the **pipe** path: the exit chunk's negative code is the signal — `−N` → name via a Unix signal-number→name table (OQ-10 resolved; POC-3 §D.2 observed `−15` → `exit-signal("TERM")` live). `core_dumped` always `false` (v1 — alktty discards `ExitStatus::core_dumped()`). **PTY path**: signal-terminated children arrive as a plain code 1 (portable-pty's exit surface; the signal number is unrecoverable there today) → `exit-status(1)` — faithful `exit-signal` on the PTY row is an alktty-backend/portable-pty ask, upstream | +| `exit_signal` | signal-terminated exits on the **pipe** path: the exit chunk's negative code is the signal — `−N` → name via a Unix signal-number→name table (OQ-10 open — direction fixed, derivation details pending; POC-3 §D.2 observed `−15` → `exit-signal("TERM")` live). `core_dumped` never asserted (working disposition, v1 — alktty discards `ExitStatus::core_dumped()`). **PTY path**: signal-terminated children arrive as a plain code 1 (portable-pty's exit surface; the signal number is unrecoverable there today) → `exit-status(1)` — faithful `exit-signal` on the PTY row is an alktty-backend/portable-pty ask, upstream | ## The structural constraint: spawn-and-return @@ -123,7 +123,7 @@ under `--all-features`. - **OQ-08** (shell convention — status: open): blocks this doc's `shell_request`/`NegotiateRequest.cmd` row detail. -- ~~OQ-10~~ (exit-signal derivation — resolved: the negative code is the signal encoding on the pipe path; the PTY path's signal-identity loss is recorded upstream, alktty-side). +- **OQ-10** (exit-signal derivation — open: the negative code is the signal encoding on the pipe path [premise corrected]; the `−1` → 255 and core-dumped-never-asserted dispositions on the `exit_status`/`exit_signal` rows here are the working draft, formalized at OQ-10's close; the PTY path's signal-identity loss is recorded upstream, alktty/portable-pty-side). - **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 8026668..d12c259 100644 --- a/docs/architecture/open-questions.md +++ b/docs/architecture/open-questions.md @@ -94,19 +94,19 @@ 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: ~~SSH `exit-signal` fidelity over the alktty exit-code convention~~ — resolved +### OQ-10: SSH `exit-signal` fidelity over the alktty exit-code convention -- **Origin**: `mapping-table.md` (exit_signal row), [poc-3-mapping-bakeoff.md](../research/poc-3-mapping-bakeoff.md) §D.2 -- **Status**: resolved — the alkssh mapping contract is decided (below, and in `mapping.md`'s exit rows). The PTY-path fidelity limit recorded in point 4 is an alktty-backend/portable-pty matter and belongs in their trackers; alkssh adapts automatically with no contract change if the convention ever holds on that path. -- **Resolution**: alkssh adds nothing to the alktty wire — the exit chunk is alktty's contract (`{"type":"exit","code":N}` on ctrl-out, exit-chunk-is-last — alktty ADR-004, ported from alknet ADR-055) — and derives all SSH fidelity from the code the chunk already carries: - - 1. **The negative code is the signal encoding on the pipe path.** alktty's pipe backend maps Unix `ExitStatus` signal termination to `−N` (`pipe.rs::exit_code_from`, `ExitStatusExt::signal() → -sig`; the alknet ADR-055 convention). POC-3 stage D observed it live on the wire: signal TERM → `−15` → SSH `exit-signal("TERM")` via the cloned `Session::handle()`. - 2. **Name derivation is alkssh server-side work, not a wire change**: `−N` → the SSH `exit-signal` name from a Unix signal-number→name table (signal semantics belong to the serving platform, where the backend reaps). `core_dumped` is always `false` in v1 — alktty discards `ExitStatus::core_dumped()`, so nothing better is knowable from the chunk; no error message rides. - 3. **`−1` is never a signal claim.** Per alktty ADR-004 §4, `−1` is the backend could-not-determine sentinel — it collides with a real SIGHUP termination (signal 1). Alkssh renders `−1` as `exit-status(255)` (OpenSSH's conventional failure value): fabricating `exit-signal("HUP")` for a wait failure would invent a signal that never happened. The SIGHUP information loss inside the sentinel is alktty's documented edge — alktty's additive `exit_error` control type is its own extension seam if it ever becomes load-bearing. - 4. **PTY path — the real fidelity limit, backend-side.** portable-pty 0.9 converts a signal-terminated child's wait status with `code = status.code().unwrap_or(1)` → **1**, keeping the signal only in a separate humanized (`strsignal`) string field that alktty's PTY waiter discards (`pty.rs` reads `exit_code()` only; the number is unrecoverable through portable-pty 0.9's trait surface). So a signal-killed PTY session arrives as a plain exit 1 → alkssh maps `exit-status(1)`; faithful `exit-signal` for the `session`/PTY row needs the PTY backend to conform to alktty's own negative-code convention — an alktty/portable-pty ask with tty/pty-grounded justification, not an alkssh convenience ask. - 5. **Non-Unix**: no signal termination; `−1` is the only negative that can appear → rule 3. - 6. **Pipe-mode pid-only vs PTY process-group signal scope** is backend semantics, not a mapping change — recorded as a fidelity note on `mapping.md`'s `signal` row. -- **Cross-references**: OQ-08, [mapping.md](mapping.md) (`exit_signal`, `exit_status`, `signal` rows), alktty ADR-004 / alknet ADR-055 +- **Origin**: `mapping-table.md` (exit_signal row), [poc-3-mapping-bakeoff.md](../research/poc-3-mapping-bakeoff.md) §D.2. **Premise corrected 2026-10-27**: the POC seed's "the exit chunk carries no signal identity — alktty might widen the chunk" framing was wrong; the negative code *is* the signal encoding (below), integrated into the seed here rather than argued after the fact. +- **Status**: open — direction fixed, derivation details pending. Direction fixed by review: alkssh derives SSH fidelity from the exit chunk alktty already carries; widening the chunk (or `TtyHandle.exit_code → i32`) is not a justified candidate by default — "easier for alkssh" is not a tty/pty-grounded justification, and the change ripples `ControlMessage::Exit`'s parsed shape, the backend API, and the alknet ADR-055 lineage. Any widening needs its own alktty ADR with the full consequence analysis. +- **The convention alkssh consumes**: `{"type":"exit","code":N}` on ctrl-out, exit-chunk-is-last (alktty ADR-004, ported from alknet ADR-055). `code` semantics: `ExitStatus::code()` on normal exit; on Unix, the negative signal number on signal termination — alktty's own convention (`ExitStatus::code()` returns `None` there), implemented per-backend (`pipe.rs::exit_code_from` via `ExitStatusExt::signal()`; POC-3 §D.2 observed it live: signal TERM → `−15` → SSH `exit-signal("TERM")` via the cloned `Session::handle()`). +- **Derivation details** (mapping-contract territory; the working dispositions live on `mapping.md`'s exit rows until formalized there / as an ADR-006 amendment): + 1. **Name derivation**: SSH `exit-signal` requires a name; `−N → name` is alkssh server-side work, not a wire change (a Unix signal-number→name table — signal semantics belong to the serving platform, where the backend reaps). + 2. **Core-dumped**: alktty discards `ExitStatus::core_dumped()`, so nothing better is knowable from the chunk; SSH's `exit-signal` carries a bool. Working disposition: never asserted, v1. + 3. **`−1` is never a signal claim.** Per alktty ADR-004, `-1` is the backend could-not-determine sentinel — and on the Unix pipe path the same value is the SIGHUP encoding (signal 1). Working disposition: `exit-status(255)` (OpenSSH's conventional failure value) — fabricating `exit-signal("HUP")` for a wait failure would invent a signal that never happened. alktty's additive `exit_error` control type is the extension seam recorded in its own ADR if the distinction ever becomes load-bearing. + 4. **Non-Unix**: no signal termination; `−1` is the only negative that can appear → rule 3. + 5. **Pipe-mode pid-only vs PTY process-group signal scope** is backend semantics, not a mapping change — recorded as a fidelity note on `mapping.md`'s `signal` row. +- **PTY path — the real fidelity limit, backend-side.** portable-pty 0.9 reaps a signal-terminated Unix child through `From`, which converts it to `code = code().unwrap_or(1)` → **1**, keeping the signal only as a locale-dependent `strsignal` string field (verified against portable-pty 0.9.0's source); the number is unrecoverable through its trait surface. alktty's PTY waiter (`pty.rs`) reads `exit_code()` only and discards that string, so a signal-killed PTY session arrives as a plain exit 1 → alkssh maps `exit-status(1)`. Faithful `exit-signal` for the `session`/PTY row needs the number — an alktty/portable-pty ask with tty/pty-grounded justification, not an alkssh convenience ask; alkssh adapts automatically with no contract change if the convention ever holds on that path. +- **Cross-references**: OQ-08, [mapping.md](mapping.md) (`exit_signal`, `exit_status`, `signal` rows), ADR-006, alktty ADR-004 / alknet ADR-055 ### OQ-11: `direct-streamlocal` platform surface — compile-time or runtime decline shape?