docs: resolve OQ-TN-14 — unix in via local, stdio out (alktty owns process stdio)

Split the last open substrate-placement question:

- Unix: ships with the local feature v1 (dial_unix — same halves
  shape as TCP; the wire enum already carried unix per ADR-001;
  the params task's schema list includes all three values)
- Stdio: OUT of scope — a spawned process's stdin/stdout/stderr IS
  alktty's pipe mode (LocalTtyBackend + tokio::process + Stdio::piped,
  alktty tty-local.md): three multiplexed logical streams + the
  exit-code control chunk (alktty ADR-004) + signal forwarding
  (REQ-TTY-02). A stdio bridge here would be alktty's runner mode
  with the terminal stripped out — a strictly worse duplicate that
  also drops the semantics that matter (a byte tunnel has neither
  exit codes nor signals). Remote command execution composes via
  alktty on the same channels substrate.

Updated: open-questions.md OQ-TN-14 (resolved), overview.md feature
gate + deps + OQ summary, producer.md OQ ref, OQ-TN-10 promotion
(#3 split), phase-0-findings + both POC summaries' resolution notes,
params task (schema enum includes unix), local-socket-halves task
(unix ships, stdio does NOT — with the composition rationale),
oq-tn-14-tracker task repurposed (boundary-maintenance: re-opens only
if a consumer needs stdio-without-process-semantics — which would
need its own ADR, or if the alktty/alktunnels boundary needs
sharpening).

Verified: taskgraph valid (12 tasks, no cycles)
This commit is contained in:
2026-09-07 19:44:03 +00:00
parent 69498b79cc
commit 3a447273a8
10 changed files with 90 additions and 52 deletions
+27 -12
View File
@@ -150,9 +150,10 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
- **Priority**: low
- **Status**: resolved — UDP POC DONE (2026-09-06, 17 tests),
reverse-flow POC DONE (2026-09-07, 14 tests; re-validated over
alkcall 0.7.0, 16 tests). #3 (unix/stdio) folds into the real
crate's test suite (pump agnosticism confirmed twice); #4 (helper
spike) resolved by alkcall ADR-050. Findings F-1 (plan payloads
alkcall 0.7.0, 16 tests). #3 split (OQ-TN-14): unix ships with the
`local` feature; stdio is OUT — alktty's pipe mode owns process
stdio (exit codes + signals are tty semantics). #4 (helper spike)
resolved by alkcall ADR-050. Findings F-1 (plan payloads
`Send + Sync` — documented upstream) and F-2 (UDP codec mandatory)
recorded; W1 filed upstream and RESOLVED (CF-005/CF-006).
- **Cross-references**: ADR-003, OQ-TN-05, OQ-TN-06
@@ -215,13 +216,27 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
### OQ-TN-14: Unix-socket and stdio substrate placement
- **Origin**: OQ-TN-10 #3 (the cheap un-POC'd item)
- **Status**: open
- **Priority**: low
- **Impacts**: the `local` feature's module surface (whether unix
dial/listen helpers ship in v1); not a wire/ADR question (the wire
enum already carries `unix` per ADR-001).
- **Resolution**: pending — both POCs confirm pump agnosticism; unix
is cheap (same halves shape as TCP); stdio bridging is a different
lifecycle (no dial — the pipe IS the resource). Fold into the
backend module task; unix leans yes for v1, stdio leans deferred.
- **Cross-references**: ADR-003, ADR-004, overview.md
- **Status**: **resolved 2026-09-07 (split by substrate):**
- **Unix: in scope for the `local` feature v1** — same halves shape
as TCP (`dial_unix`), cheap, wire enum already carries `unix`
(ADR-001). Implemented by `tunnels/local-socket-halves`.
- **Stdio: OUT of scope — alktty owns it.** A spawned process's
stdin/stdout/stderr IS a terminal session in alktty's model:
`LocalTtyBackend`'s pipe mode (`tokio::process::Command` +
`Stdio::piped()`, alktty `tty-local.md`) is exactly the stdio
case — three multiplexed logical streams + the exit-code
control chunk (alktty ADR-004) + signal forwarding (REQ-TTY-02).
A "stdio bridge" here would be alktty's runner mode with the
terminal stripped out: a strictly worse duplicate that also
drops the semantics that matter (exit code, signals — a byte
tunnel has neither). Consumers needing to run commands remotely
compose: alkcall dial → alktty session (pipe mode) — the two
crates are siblings on the same channels substrate, not
competitors. (The OQ-TN-10 #3 "cheap item" was scoped before the
sibling relationship was this clear.)
- **Impacts**: `tunnels/local-socket-halves` (unix yes, stdio no);
the wire enum's `unix` value now has a v1 implementation path.
- **Cross-references**: ADR-003, ADR-004, overview.md; alktty
`tty-local.md` §pipe mode, alktty ADR-004 (exit code on a control
chunk)
+4 -3
View File
@@ -101,7 +101,7 @@ alktunnels (default — wasm-clean)
└── (no backend deps — sockets/process live in feature-gated modules)
alktunnels (local feature) — non-wasm by design
└── adds: tokio/net (TCP dial, UDP bind/connect, unix sockets — unix pending OQ-TN-14)
└── adds: tokio/net (TCP dial, UDP connect, unix sockets — unix per OQ-TN-14)
```
Same posture as alktty: the default crate is protocol-only and
@@ -115,7 +115,7 @@ trivial here, since the codec is a 2-byte length prefix).
| Feature | Contents | Wasm |
|---------|----------|------|
| *(default)* | params, wire codec, open-op spec, establisher shapes, `TunnelSession` — protocol only | yes |
| `local` | TCP/UDP/unix dial + listen helpers — halves-producing dial/listen functions (ADR-004; unix pending OQ-TN-14) | no |
| `local` | TCP/UDP/unix dial + listen helpers — halves-producing dial/listen functions (ADR-004). Unix ships (OQ-TN-14); stdio does NOT — a spawned process's stdio is alktty's pipe mode (exit codes, signals), not a tunnel | no |
## Module Map
@@ -157,7 +157,8 @@ Key questions affecting this document:
- **OQ-TN-11**: resource naming collision domain + lifecycle (partially resolved)
- **OQ-TN-12**: hub re-produce composition helper (deferred(scope))
- **OQ-TN-13**: UDP truncation semantics (resolved — fail-loud, ADR-003)
- **OQ-TN-14**: unix/stdio substrate placement (open)
- **OQ-TN-14**: unix/stdio placement (resolved — unix in via
`local`, stdio out: alktty's pipe mode owns process stdio)
## References
+2 -2
View File
@@ -177,8 +177,8 @@ Tunnels reach local networks — the open gate is the security boundary
- **OQ-TN-11**: resource naming collision domain + lifecycle
(partially resolved — the per-producer registry is the leading
candidate)
- **OQ-TN-14**: unix/stdio substrate placement (open — backend module
task, not a wire question)
- **OQ-TN-14**: resolved (split: unix in, stdio out — alktty's pipe
mode owns process stdio; see open-questions.md)
## References
+4
View File
@@ -35,6 +35,10 @@ this document is the normative WHAT.
(NOT an address; the producer's registry maps it to its backing).
- `substrate`: string, required, enum `["tcp", "udp", "unix"]` — the
extensible discriminator (ADR-001); selects the data-plane framing.
The schema ships with all three values in v1 (unix has an
implementation path — OQ-TN-14). An older producer rejecting a
newer substrate value is the SSH "unknown channel type" posture —
loud, not silent.
- `input_schema` pins both as required strings; unknown substrate
values fail schema validation (typed error — loud, not silent).
- Extensibility: a new substrate is a new enum value, not a format
+5 -4
View File
@@ -886,10 +886,11 @@ Candidate reading for the research specialist (to be expanded):
(2026-09-07, `reverse-poc-summary.md` — the `-R` shape validated
end-to-end over alkcall 0.6.0, re-validated over 0.7.0 with the
CF-005/CF-006 identity remediation; findings F-1/F-2; W1 filed
upstream and RESOLVED). Remaining items (#3 unix/stdio, #4
helper spike) are spec-scope: #3 folds into the real crate's
tests (pump agnosticism confirmed twice), #4 resolved by
alkcall 0.6.0 (ADR-050)
upstream and RESOLVED). Remaining items resolved in Phase 1:
#3 split — unix ships with the `local` feature (OQ-TN-14);
stdio is OUT (alktty's pipe mode owns process stdio — exit
codes + signals are tty semantics); #4 resolved by alkcall
0.6.0 (ADR-050)
- [x] Two-pump helper convergence input gathered (OQ-TN-06) — the POC
gives both shapes (producer `pump_halves`, consumer
`take_halves` + copy): same shape modulo channel side; the
+3 -1
View File
@@ -293,7 +293,9 @@ review and ADRs:
2. **Unix-socket and stdio substrates** — the OQ-TN-10 #3 "cheap"
item. The pump is substrate-agnostic by construction (finding #5);
these are wiring, not mechanics. Fold into the real crate's test
suite rather than a separate POC.
suite rather than a separate POC. (Phase 1 resolution: unix in via
the `local` feature; stdio OUT — alktty's pipe mode owns process
stdio. OQ-TN-14.)
3. **The reverse-flow (`-R`) advertisement** — OQ-TN-10 #2 remains
open; the SSH `tcpip-forward` template in the survey is the
lifecycle shape to validate there.
+3 -1
View File
@@ -223,7 +223,9 @@ alktunnels-reverse-poc/
listen establisher over the same open op — spec-shaped, not POC-
shaped.
3. **Unix socket + stdio substrates** — OQ-TN-10 #3 (cheap; the pump
is substrate-agnostic and both POCs now confirm it).
is substrate-agnostic and both POCs now confirm it). Phase 1
resolution: unix in via the `local` feature; stdio OUT — alktty's
pipe mode owns process stdio (OQ-TN-14).
4. ~~**W1 remediation**~~**resolved 2026-09-07 (alkcall 0.7.0)**:
the remediation IS now exercised from this POC's topology
(transport identity, `ServingConfig.identity` override, precedence,
+30 -22
View File
@@ -1,52 +1,60 @@
---
id: architecture/oq-tn-14-tracker
name: "[external-trigger, deferred-oq] OQ-TN-14 unix/stdio placement tracker"
name: "OQ-TN-14 follow-through: unix lands with local v1; stdio stays alktty's"
status: pending
depends_on: []
scope: single
risk: trivial
impact: isolated
level: research
tags: [architecture, deferred-oq, external-trigger, unix, stdio]
tags: [architecture, external-trigger, unix, stdio, alktty]
---
## Description
External-trigger tracker for OQ-TN-14 (`docs/architecture/open-questions.md`
— unix-socket and stdio substrate placement, currently `open` leaning
unix-yes-v1). This task is NOT actionable implementation work; it tracks
whether the unblocking condition has arrived. If the condition arrives, this
task flips the OQ (and the implementation task `tunnels/local-unix-halves`
below) from its deferred/open posture to actionable.
OQ-TN-14 is resolved (open-questions.md, 2026-09-07): unix ships with
the `local` feature v1 (`tunnels/local-socket-halves`); stdio is OUT of
scope — a spawned process's stdio is alktty's pipe mode (exit codes +
signals are tty semantics, not tunnel semantics); remote command
execution composes via alktty.
Unblocking conditions (any one):
This task stays open as the boundary-maintenance tracker (research
level — not implementation work). It re-opens the question only if a
concrete consumer demonstrates the resolved posture is wrong:
1. A consumer needs unix-socket tunneling through alktunnels (a hub exposing
a docker container's unix socket, or a local daemon bridge).
2. The `tunnels/crate-init` task lands and the `local` feature module needs
its v1 surface finalized (the decision point baked into that task).
1. A consumer needs stdio tunneling with NO process semantics (no exit
code, no signals) — e.g. piping an already-running long-lived
process's existing stdio. That is a different resource shape (the
pipe IS the resource, no dial); it would need its own ADR, not a
silent addition to `local`.
2. A consumer reports confusion about when to use alktunnels vs alktty
for a stdio-like flow — the composition boundary in OQ-TN-14 needs
sharpening in the docs, not code.
If neither arrives by the end of the v1 implementation phase, the OQ stays
open and `local` ships TCP+UDP only (the lean posture ADR-004 endorses).
If neither arrives, this task closes with the implementation phase.
## Work
- Re-read OQ-TN-14 + `docs/architecture/overview.md` §Feature Gates when
triaging; confirm whether the unix helper ships with `local` v1.
- Re-read OQ-TN-14 + alktty's `tty-local.md` §pipe mode at triage;
confirm the boundary held (no stdio-shaped resource got registered
as a tunnel resource).
## Verification
- [ ] Tracked (this file existing and being reviewed at triage time IS the
tracking mechanism — no code or doc change is expected until the
condition arrives)
- [ ] Tracked (this file existing and being reviewed at triage time IS
the tracking mechanism — no code or doc change is expected unless
a boundary condition above arrives)
## Out of scope
- Implementing the unix/stdio helpers (see `tunnels/local-unix-halves`)
- Implementing stdio bridging (would need its own ADR per the
conditions above)
## References
- docs/architecture/open-questions.md OQ-TN-14
- docs/architecture/open-questions.md OQ-TN-14 (the resolution)
- alktty `docs/architecture/tty-local.md` §pipe mode (the owner of
process stdio)
- docs/sdd_process.md §Deferred OQs and the blocker-task half
## Notes
+4
View File
@@ -47,6 +47,10 @@ pub struct UdpAssociateHalves { sock: Arc<UdpSocket> } // the POC's associate s
`poll_recv` into a too-small caller buffer must fail loud
(OQ-TN-13's resolved posture — ADR-003). The codec wraps at this
boundary; the pump never sees UDP specifics.
- **Unix**: `dial_unix` ships (OQ-TN-14 resolved — same halves shape
as TCP). **Stdio does NOT** — a spawned process's stdio is alktty's
pipe mode (exit codes + signals are tty semantics, not tunnel
semantics); remote command execution composes via alktty, not here.
- **Error mapping** to `TunnelEstablishError` variants (the POC's
`Into<EstablishmentError>` path).
- `TargetHandle` re-used from producer.rs (the halves type lives in
+8 -7
View File
@@ -34,12 +34,12 @@ pub enum Substrate { Tcp, Udp, Unix }
- `deny_unknown_fields` is the loud-rejection posture (ADR-001: unknown
fields fail schema validation, never silently ignored).
- `Substrate::Unix` is included in the type (the wire enum per ADR-001)
but NOT in the open-op schema's enum list v1 (`["tcp", "udp"]`) until
OQ-TN-14 resolves — an older producer rejecting a newer substrate is
the SSH "unknown channel type" posture; the schema list is the
authoritative gate. Document this asymmetry in the type docs (type
extensible, schema conservative).
- `Substrate::Unix` is included in the type AND in the open-op schema's
enum list v1 (`["tcp", "udp", "unix"]` — OQ-TN-14 resolved: unix has
an implementation path via the `local` feature). The schema list is
the authoritative gate for newer-substrate negotiation: an older
producer rejecting a newer substrate value is the SSH "unknown
channel type" posture (loud, not silent).
### The open-op spec (in producer.rs or params.rs — put it where
`tunnels/producer-open-op` can consume; the spec builder lives in
@@ -56,7 +56,8 @@ pub fn tunnel_open_spec() -> OperationSpec
- `OperationType::Sub`; `Visibility::External`; channel-open marker
`ChannelOpenSpec::new(TUNNEL_ALPN)`.
- `input_schema`: `{resource: string (required), substrate: string enum
["tcp","udp"] (required)}` — JSON Schema Draft shape as the POCs used.
["tcp","udp","unix"] (required)}` — JSON Schema Draft shape as the
POCs used (unix in the list per OQ-TN-14's resolution).
- `output_schema`: `{channel_id: integer > 0}`.
- `AccessControl.required_scopes: [TUNNEL_OPEN_SCOPE]` (ADR-006).
- `description`: a one-line human hint (round-trips through discovery;