docs(architecture): OQ-10 premise corrected — the negative exit code IS the signal identity

- 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)
This commit is contained in:
glm-5.3-flash committed 2026-10-27 14:08:14 +00:00
1 parent ff2d69c67b
commit 9217a8b3cf
5 files changed
+43 -13

No files matched your search

+2 -4
View File
@@ -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).
+16 -6
View File
@@ -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?
+2 -2
View File
@@ -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
+18
View File
@@ -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`);
+5 -1
View File
@@ -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