docs/superpowers/plans/2026-09-07-native-port-forwarding-slice-1.md
Ref: Size: 17.7 KiB History
# Native local TCP forwarding — slice 1
## Behaviour and scope
- `muxg --forward LOCAL_PORT:REMOTE_PORT TARGET` accepts repeatable, non-zero
TCP port pairs. Forwarding requires one explicit entry transport (`TARGET`,
`--sock`, or `--via`) and applies only to this invocation.
- Each unique rule binds `127.0.0.1:LOCAL_PORT`. Accepted streams connect on
the daemon host to `127.0.0.1:REMOTE_PORT`; names, DNS, wildcard binds,
reverse forwarding and UDP are deliberately absent.
- One process-owned forwarding manager uses one dedicated mux connection for
the entry target, independent of pane focus, terminal attachment, session
creation and geometry. Duplicate rules are deduplicated. A bind collision
or invalid/unsupported peer is reported visibly without damaging terminal
attachments.
- The manager remains while any pane has the same concrete target identity;
focus and session do not matter. Replacing/removing the final matching pane
releases its listeners, while another matching pane keeps them alive.
- A lost mux connection keeps the local listening sockets reserved, resets
live streams, refuses new accepts while unavailable, and redials. The
dedicated handoff target never repeats an entry target's start request or
narration.
- The daemon admits forwarding peers in a small table separate from the 32
interactive client slots. A forwarding peer cannot consume a terminal
session or displace a full workspace.
## Wire and resource contract
- The existing byte-stream transport remains opaque and single-stream.
Forward-role hello/ready frames identify the connection before any channel
frame is accepted, with a bounded client handshake wait and explicit
unsupported-role failure.
- Client-chosen non-zero channel IDs carry open/open-result, data, credit,
half-close and reset frames. Data payloads and credit are bounded. Both
peers cap channel counts, staged transport bytes, and per-channel queues.
- TCP listeners and streams are nonblocking. Connect completion is checked
with `SO_ERROR`; accept/read/write and frame work have per-pump budgets so a
busy tunnel cannot monopolize the daemon or native event loop.
- EOF is propagated as a half-close after queued bytes drain. Invalid role,
malformed frames, cap violations and channel-local failures reset a channel
or close the forwarding peer without entering terminal frame handling.
## Ownership and files
- Shared wire: `src/engine/protocol.zig`.
- Shared client: a forwarding child module under `src/client/`, exported from
`src/client/client.zig`; it owns listener/channel state and its worker.
- Daemon: a relay child module under `src/server/`, integrated into
`src/server/server.zig` observer promotion, admission, polling and teardown.
- Native: `src/gui/runtime.zig`, `src/gui/frame.zig`, `src/gui/native_core.zig`
and `src/cli/muxg.zig` for process lifetime and option plumbing.
- Coordination/docs: `build.zig` only if module wiring needs it, plus
`README.md` and focused tests. Terminal-wall files and existing agent
forwarding semantics remain untouched.
## Acceptance checks
- Protocol and unit tests cover codecs, malformed/oversized frames, duplicate
rules, local collisions, channel caps, credit/backpressure, refusal,
half-close, channel isolation and unavailable-peer behaviour.
- Daemon/client integration tests use loopback sockets for request/response,
streaming and concurrent channels, and assert the dedicated role creates no
session and does not alter geometry or interactive admission.
- Native-core tests cover manager lifetime independent of focus and session,
final-matching-pane removal, replacement and deduplication, and
entry-target scoping. Where a real window is available, an isolated native
journey checks CLI plumbing and reconnect. Direct QUIC and stdio-proxy
forwarding paths are core integration evidence; external SSH and macOS are
reported as unavailable where their environments are not present.
- Run focused `make daemon-test client-test native-core-test`, `make native`,
then required `make check` and `make ci`; use `tools/run-logged.sh` for noisy
gates and isolated daemon state for real-binary checks. Run `make
native-e2e` when the display environment supports it. Linux results do not
claim macOS coverage.
## Delegation
- Implementation and focused validation: delegated worker on `gpt-5.6-sol`
at high reasoning effort, owning the files above.
- Independent review: separate `gpt-6-astra` worker at high reasoning effort;
any review-fix brief is assigned by the
coordinating agent; this implementation worker does not self-review as the
independent reviewer.
## Validation
Validation snapshot: `HEAD` `03661841b1ef0853f43317067d4894ebe355a937`; the
tracked-diff SHA-256 was
`57aa4b5573d9d3dba3d9f1b808ae8b2f251654ee6d176a303a850707ec09aa90`.
New-file SHA-256 values: `src/client/forward.zig`
`e2b2a9c10c3e8f3b65fea6df67abecac552ff9eba238972fe73cb0f6a749b0ec`,
`src/server/server_forward.zig`
`93dc340496878855aeb87bb2890ee7fdf00fc276c2a629e01cd761361f1519d3`,
`test/native_forward.py`
`140100e52d2d48773322a23aff4af1e22be41b7c391277bde3755dd9288a6502`, and
`test/native_forward_remote.py`
`d401d2f1e4efbea0574aa4b0975fc5d8d4f3bc53ba24dc9a5a279b7f7e4ab0c8`.
The complete tracked-path list, platform, display environment, and tool
availability are in `dist/native-forward-validation/baseline.log`. The host was Linux
`charizard` x86_64 with `DISPLAY=:0`, `WAYLAND_DISPLAY=wayland-1`, and
`XDG_RUNTIME_DIR=/run/user/1000`. Make resolved the pinned Zig 0.15.2 through
mise at `/home/xanderle/.local/share/mise/installs/zig/0.15.2/bin/zig`;
`deps/zig/zig` itself was unavailable. Python 3.14.7, ShellCheck, Node,
pkg-config, and Make were available.
All noisy commands used `tools/run-logged.sh`; each status below is the actual
wrapper-preserved exit status, and its complete output is retained at the named
path. Builds ran serially.
| Command | Status | Log |
| --- | ---: | --- |
| `python3 -c` AST parse of `test/native_forward.py` and `test/native_forward_remote.py` | 0 | `dist/native-forward-validation/python-syntax.log` |
| `make daemon-test client-test native-core-test` | 0 | `dist/native-forward-validation/focused.log` |
| `make native` | 0 | `dist/native-forward-validation/native.log` |
| `make install BINDIR="$PWD/dist/native-forward-validation/release/bin" INSTDIR="$PWD/dist/native-forward-validation/release/stage"` | 0 | `dist/native-forward-validation/release-build.log` |
| release-binary version check | 0 (`muxg` reported `ReleaseSafe`) | `dist/native-forward-validation/release-versions.log` |
| `python3 -B test/native_forward.py .../release/bin/mux .../release/bin/muxg` | 0 (4 checkpoints) | `dist/native-forward-validation/local-forward.log` |
| `make check` | 0 | `dist/native-forward-validation/check.log` |
| `make ci` | 0 | `dist/native-forward-validation/ci.log` |
| `make native-e2e` | 0 (33 checkpoints) | `dist/native-forward-validation/native-e2e.log` |
`make native` alone produces the ordinary native build, whereas
`native_forward.py` explicitly requires ReleaseSafe or ReleaseFast binaries.
The isolated `make install` invocation above was therefore additionally needed
to produce matching ReleaseSafe `mux` and `muxg` binaries without installing
into the user bin directory. The local forwarding script exercised socket,
stdio, and QUIC entry routes. Its log also contains a fixture-thread
`OSError: [Errno 107] Transport endpoint is not connected`; the driver itself
completed its four checkpoints and exited 0, so this is recorded rather than
silently treated as a separate passing assertion.
`test/native_forward_remote.py` was **not executed** and no remote host was
contacted. Syntax-only AST parsing exited 0. Independent review still marks
that driver unsafe: its `start -d` argument order is invalid; the SSH wrapper
cannot handle handoff `PATH` assignment; setup inherits `PATH`; PID
publication/ownership is unsafe; and cleanup races GUI reconnect and ignores
errors. Consequently real SSH forwarding, including remote QUIC, remains
unvalidated. The reviewer also found no black-box admission snapshots. The
slow-reader leg is only a responsiveness smoke check, not a backpressure
oracle; recovery is whole-daemon rather than forwarding-only; and the stale
local comment around line 259 claiming the dedicated connection does not
repeat an entry target is not validation evidence.
Unconfirmed production concerns remain: no connect deadline, hot handshake
retry, late QUIC receive cap, and one-shot QUIC admission. A user-reported
manual Python `http.server` plus browser success is recorded only as an
unverified transport detail, not as acceptance evidence. No macOS result is
claimed.
### Remote SSH and direct-QUIC forwarding revalidation
The opt-in check ran against the user-authorized disposable
`ubuntu@192.168.0.107` VM (Linux 7.0.0-31-generic x86_64). The existing local
ReleaseSafe pair at `dist/native-forward-validation/release/bin/{mux,muxg}`
was used without rebuilding: both are x86-64 Linux binaries and `muxg --version`
reported `ReleaseSafe` (`remote-binary-platform.log`). The remote fixture
copied that `mux` into a private mode-0700 `mktemp` root, generated one
fixture-owned key locally and copied it mode 0600, and used private HOME/XDG,
Unix socket, services, and a random high (40000--59999) UDP port. It copies
and sources `test/os_oracle.sh` remotely.
| Command | Status | Log/evidence |
| --- | ---: | --- |
| Python AST syntax parse of both forwarding scripts | 0 | `dist/native-forward-validation/remote-python-syntax.log`, `remote-python-syntax-status.txt` |
| `MUX_FORWARD_REMOTE_ENABLE=1 python3 -B test/native_forward_remote.py dist/native-forward-validation/release/bin/mux dist/native-forward-validation/release/bin/muxg` | 0 | `dist/native-forward-validation/remote-forward.log`, `remote-forward-command.txt`, `remote-forward-status.txt` |
| ReleaseSafe/version/architecture and remote-platform record | 0 | `dist/native-forward-validation/remote-binary-platform.log` |
The first route is regression coverage for actual muxg stdio proxying:
`muxg --forward --via "ssh ubuntu@192.168.0.107 /absolute/fixture/mux d proxy
--sock /absolute/fixture/mux.sock"`; it uses neither `ssh -L` nor a UDP proxy.
The second is a direct GUI connection to
`quic://192.168.0.107:HIGH_PORT --key FIXTURE_KEY`, with no SSH tunnel in that
route. Both routes asserted an exact `HTTP/1.0 200` response and unique body,
three concurrent random streams larger than 1 MiB with SHA-256 records in the
log, exact bidirectional bytes, TCP half-close EOF, and local listener
reservation. The direct-QUIC route additionally stopped and restarted the
whole private daemon, confirmed listener reservation while unavailable, then
confirmed exact HTTP recovery. This is explicitly whole-daemon recovery, not
forwarding-only recovery.
After each start, the remote OS oracle identifies exactly one daemon PID only
when its executable is the copied fixture `mux` and it owns the fixture Unix
socket. It separately observes the concrete QUIC UDP endpoint bound via
`udp_local_bound`; this does not attribute UDP ownership to that PID. Stop
success alone is not accepted: after the restart and at final cleanup the
oracle requires the recorded PID gone, Unix socket absent, and UDP listener
released. The GUI quits before final stop. The successful log ends
`PASS: remote fixture cleanup (daemon PID/socket/UDP released; services and
root removed)`; service termination and root deletion are independently
verified. There were no retained remote fixture paths or cleanup errors.
Fresh SHA-256 values are in
`dist/native-forward-validation/remote-validation-hashes.txt`: corrected
`test/native_forward_remote.py` is
`96686da6fc048c08b4df6398a7028326a87263cfdd3057150a553b135c2acc96`, unchanged
`test/native_forward.py` is
`140100e52d2d48773322a23aff4af1e22be41b7c391277bde3755dd9288a6502`, and the
current complete `src/` working-tree manifest is
`7d4e114be65dbc0ac501d8a2978df9fefde34ca63fda23ca6ade489d7569e580`.
The manifest records the pre-existing dirty production snapshot; this work made
no production-file changes. Protocol/platform coverage is Linux x86-64 only:
SSH stdio and direct IPv4 QUIC were exercised; macOS was not exercised. No
general build or gate was rerun because this delivery changes only the Python
harness and validation record.
### Final clean landing candidate (2026-09-08)
This section is final-candidate evidence; the preceding sections are historical
records and are intentionally not erased. A new clean worktree was created
from original `input-refactor` `22fa3b7f878204a1d785b8293c53d04dd810fb58` and
rebased with `GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash 03661841`.
The production snapshot is the resulting one-commit candidate
`8d4f4e41213548711d29ed7d26c8c4f41c367f97` (parent
`03661841b1ef0853f43317067d4894ebe355a937`), tree
`181377d85715a59355d0ce2d349376e8cc085f65`. The pre-squash tree was the same
value, so tree equality passed; the eight original range commits became one
production commit without changing tracked content. The base-to-candidate binary-diff SHA-256 is
`c476c4b065e5de246e660561a3f6ae599ac12d38f45425ec2b336992aed22d3c`.
No complete-index-manifest hash is asserted here: the originally recorded
value did not have a reproducible manifest artifact.
All commands below ran serially from that clean candidate through
`tools/run-logged.sh`, with wrapper-preserved status **0**. Full ignored logs,
status files, source-index snapshot, platform/toolchain record, dependency
symlink provenance, and release-binary records are retained outside Git at
`dist/native-forward-validation/final-candidate/`; this document retains the
assertions rather than making the ignored logs the only evidence. The host was
Linux x86-64; Make resolved mise Zig 0.15.2. No macOS result is claimed.
| Command | Status | Final assertion/result |
| --- | ---: | --- |
| `make daemon-test client-test native-core-test` | 0 | Focused daemon, client, and native-core suites passed. |
| `make native` | 0 | Native build and native test passed. |
| `make check` (before this documentation edit) | 0 | Required lint, format, unit, shell, ABI, reference, and source-rule gate passed. |
| `make ci` | 0 | CI, e2e, agent, and throughput gates passed. |
| `make check` (post-review documentation correction) | 0 | Re-ran after the wording correction; log and wrapper status are `dist/native-forward-validation/final-candidate/post-review-doc-check.log` and `post-review-doc-check.status`. |
| `make native-e2e` | 0 | Native journey passed all 33 checkpoints. |
| `make install BINDIR=ABSOLUTE_CANDIDATE_PATH INSTDIR=ABSOLUTE_CANDIDATE_PATH` | 0 | Built fresh ReleaseSafe `mux`/`muxg` only under the candidate; no user bin was used. |
| `python3 -B test/native_forward.py ABSOLUTE_CANDIDATE_MUX ABSOLUTE_CANDIDATE_MUXG` | 0 | Socket, stdio, and QUIC routes passed HTTP 200 with unique-token containment; each route ran three concurrent roughly 256 KiB streams (without half-close), then a separate 1 MiB+17-byte half-closed stream. Lifetime/refusal and whole-daemon recovery checks also passed (4 checkpoints). |
| `MUX_FORWARD_REMOTE_ENABLE=1 MUX_FORWARD_REMOTE=ubuntu@192.168.0.107 python3 -B test/native_forward_remote.py ABSOLUTE_CANDIDATE_MUX ABSOLUTE_CANDIDATE_MUXG` | 0 | Authorized disposable-VM SSH-stdio and direct IPv4 QUIC routes passed exact HTTP, three concurrent >1 MiB half-close streams, listener reservation, restart recovery, and OS-oracle daemon/socket/UDP-release cleanup assertions. |
The fresh candidate ReleaseSafe binaries had SHA-256 `mux`
`f58e53bd63e2bef7cac995ca3b1b82ae8ab14d65fb690d013dfa69b7baa3b544` and
`muxg` `f76e39c72e5c8fefed450915fe801745f2d0f94bc5ad3f71d52bc12214c3eb40`.
Final source SHA-256 values were `src/client/forward.zig`
`4d81ce5b896c3d734372f2f1017fffb661e63cf413205ed0b4dd177131665f30`,
`src/server/server_forward.zig`
`90d0b41d6f6035659ea1294cc257ad46eda87035e68d1b7ab3330aa903437e61`,
`test/native_forward.py`
`b9e4e96d2cd1c83063d74a8957c48542f374d1bdac904ecbef3d3943d654796e`, and
`test/native_forward_remote.py`
`c40eb5ab7067b0c86b238ea8437fcab913844f66d18bbd20dda673eabbcf2cc4`.
The latest independent review was clear. Historical remote-harness blockers
are resolved by fixture-owned private paths/key and OS-oracle cleanup; the
remote result above is the replacement evidence. Its local and remote claims
are intentionally distinct: only the remote SSH-stdio and direct-QUIC run
asserted exact HTTP response bodies, three concurrent >1 MiB half-closed
streams, and exact bidirectional bytes. The earlier assessment that
TUI handling was redundant was wrong: forwarding protocol messages must be
explicitly skipped by the TUI router because it has no reply of its own and
those frames belong to another client conversation. The committed exhaustive
router test covers every forwarding message. The historical local-HTTP
readiness failure was also fixed: startup retries now apply only to the typed
pre-response connection/reset/EOF condition; the parser API returns
`(status, body)`, requires one bounded `Content-Length`, rejects malformed,
duplicate, chunked, truncated, and surplus responses, and regression tests
prove parser failures and non-200/wrong-body responses are not swallowed.
Before this documentation-only change, the candidate source tree was clean and
had the production snapshot above. After the documentation commit, the exact
tracked diff must be this single plan file only. Read-only recomputation
against production commit `8d4f4e41` (commands and output at
`dist/native-forward-validation/final-candidate/recomputed-production-hashes.txt`)
confirms the tree equality, binary-diff hash, and listed source-file hashes.
The independent-review record is
`dist/native-forward-validation/final-candidate/independent-review.txt`.
There is no captured historical local invocation manifest; it is not claimed
as run evidence. `make check` is rerun after the edit and records the final
documentation gate.