diff --git a/docs/architecture/open-questions.md b/docs/architecture/open-questions.md index b5144b2..553738c 100644 --- a/docs/architecture/open-questions.md +++ b/docs/architecture/open-questions.md @@ -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 \ No newline at end of file +- **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) \ No newline at end of file diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index cf344d0..ab2c265 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -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 diff --git a/docs/architecture/producer.md b/docs/architecture/producer.md index f828885..bb4c758 100644 --- a/docs/architecture/producer.md +++ b/docs/architecture/producer.md @@ -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 diff --git a/docs/architecture/wire.md b/docs/architecture/wire.md index 34fd2a9..72dfb68 100644 --- a/docs/architecture/wire.md +++ b/docs/architecture/wire.md @@ -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 diff --git a/docs/research/phase-0-findings.md b/docs/research/phase-0-findings.md index 3d7d7d7..ec0c35f 100644 --- a/docs/research/phase-0-findings.md +++ b/docs/research/phase-0-findings.md @@ -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 diff --git a/docs/research/poc-summary.md b/docs/research/poc-summary.md index caf48d5..0d5712f 100644 --- a/docs/research/poc-summary.md +++ b/docs/research/poc-summary.md @@ -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. diff --git a/docs/research/reverse-poc-summary.md b/docs/research/reverse-poc-summary.md index 590f80c..df2b170 100644 --- a/docs/research/reverse-poc-summary.md +++ b/docs/research/reverse-poc-summary.md @@ -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, diff --git a/tasks/architecture/oq-tn-14-tracker.md b/tasks/architecture/oq-tn-14-tracker.md index 3e60d9d..8fee95e 100644 --- a/tasks/architecture/oq-tn-14-tracker.md +++ b/tasks/architecture/oq-tn-14-tracker.md @@ -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 diff --git a/tasks/tunnels/local-socket-halves.md b/tasks/tunnels/local-socket-halves.md index 3794e37..3f67747 100644 --- a/tasks/tunnels/local-socket-halves.md +++ b/tasks/tunnels/local-socket-halves.md @@ -47,6 +47,10 @@ pub struct UdpAssociateHalves { sock: Arc } // 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` path). - `TargetHandle` re-used from producer.rs (the halves type lives in diff --git a/tasks/tunnels/params.md b/tasks/tunnels/params.md index 000fd05..192547b 100644 --- a/tasks/tunnels/params.md +++ b/tasks/tunnels/params.md @@ -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;