alkcall 0.7.0 resolved every open downstream-consumer finding, including this crate's reverse-flow POC W1 (filed as ledger CF-005, with the CF-006 corollary): - CF-005 (a): ServingConfig.identity — the explicit override - CF-005 (b): transport Connection::identity() propagates to channel 0 — set_identity before dialing is the mTLS/QUIC path - CF-006: the establisher/pump handler receive the per-call opener identity (dispatch-resolved), not the install-time context - CF-007: ADR-016 code list completed (doc-only) - F-1 (POC finding): the ChannelPlan type doc now carries the Send + Sync payload constraint alktunnels updates: - Cargo.toml: alkcall 0.6.0 -> 0.7.0 (lockfile resolves 0.7.0) - AGENTS.md convention 11: pin note updated; the 0.7.0 identity surface marked load-bearing (precedence order + per-call opener) - AGENTS.md architecture context: CF-005/CF-006 row added; the 'What the POC does NOT settle' list replaced with the accurate settled state (Phase 0 complete; Phase 1 = the spec) - reverse-poc-summary: W1 marked RESOLVED with the remediation validated from the POC's topology (16 tests over 0.7.0: transport identity alone authorizes, ServingConfig.identity overrides, identity-less fails closed, CF-006 witness); F-1 marked documented upstream; what-the-POC-does-not-validate and verification blocks updated - phase-0-findings OQ-TN-10: reverse POC entry notes the 0.7.0 re-validation and W1 resolution The reverse POC (/workspace/alktunnels-reverse-poc, 8b35818) re- validated: the default harness now authorizes on transport identity alone — the 0.6.0-era auth_token workaround is dead code. Verification: cargo test, clippy --all-targets -D warnings, fmt --check, wasm32 check, doc --no-deps — all clean
266 lines
14 KiB
Markdown
266 lines
14 KiB
Markdown
---
|
||
status: complete
|
||
last_updated: 2026-09-07
|
||
---
|
||
|
||
# alktunnels: Reverse-Flow (`-R`) Tunnel POC Research Summary
|
||
|
||
**Status:** Research complete — the `-R` shape (the hub opens tunnel
|
||
channels *toward* a connect-side worker that serves its own open op)
|
||
validated end-to-end over alkcall 0.6.0 (2026-09-07 pass, 14 tests)
|
||
and re-validated over alkcall 0.7.0 (16 tests) after the CF-005/CF-006
|
||
identity remediation landed — the W1 `auth_token` workaround is dead;
|
||
the primary caller-identity path is transport identity. Clippy
|
||
`-D warnings` clean; fmt clean. The review 007 non-finding trace
|
||
("no upstream mechanism is missing") is confirmed by execution —
|
||
and the POC surfaced two real findings the forward POC could not
|
||
(one an upstream sharpness question — F-1, documented upstream in
|
||
0.7.0; one a spec-mandate — F-2).
|
||
**Date:** 2026-09-07
|
||
**Scope:** Validates the OQ-TN-10 #2 POC — the SSH `tcpip-forward`
|
||
template (register → per-accept channel-open → cancel) mapped onto
|
||
alkcall's both-sides serving semantics. Probes four wrinkles (W1..W4)
|
||
selected from the mechanism trace; findings F-1/F-2 below.
|
||
|
||
---
|
||
|
||
## Executive summary
|
||
|
||
A POC (`alktunnels-reverse-poc`, `/workspace/alktunnels-reverse-poc`)
|
||
validated the reverse flow end-to-end over alkcall 0.6.0 (re-validated
|
||
over 0.7.0 — see §W1):
|
||
|
||
1. **The worker (connect side, serving side)** — dials the transport,
|
||
`ChannelClient::from_connection_with_serving` +
|
||
`ChannelOperations::register_on` (generic channel ops on the
|
||
serving registry, so the hub can `channel/close`) + post-hoc
|
||
`register_openable_with_establisher` (W2). Its establisher dials
|
||
the resource target and returns the handle via
|
||
`Establishment::new(plan)` — R-01's plan flow, no handoff map.
|
||
Its pump handler awaits `alkcall::channels::pump_bidi` inline
|
||
(R-02/R-03). This is ADR-022 §2 both-sides semantics exercised
|
||
for real: connect side AND serving side.
|
||
2. **The hub (accept side, reverse-flow initiator)** —
|
||
`ChannelsAdapter` + install hook that captures the hub's channel-0
|
||
`CallConnection` and `ChannelManager`. Per local accept: call the
|
||
worker's open op (`call_with_payload` — the caller identity rides
|
||
the transport identity by default (0.7.0); the payload
|
||
`auth_token` is the optional hub-forwarding path), adopt the
|
||
worker-allocated channel ID, pump the accepted socket against the
|
||
adopted halves via `pump_bidi` spawned locally.
|
||
3. **The ADR-047 §5 orientation under test holds:** the reverse
|
||
channels are allocated by the worker (connect side — odd IDs) and
|
||
adopted by the hub (accept side). "Connection owner allocates"
|
||
exercised in the Pub-like orientation.
|
||
4. **16 tests (0.7.0 pass):** TCP round trip, 1 MiB backpressure,
|
||
half-close semantics, concurrent same-resource channels (the R-01
|
||
race-killer proven: two opens of one resource, distinct channels,
|
||
distinct dialed handles), UDP round trip, TCP+UDP concurrent,
|
||
typed establishment errors (`unknown_resource`, `dial_failed`),
|
||
the identity precedence chain (transport identity alone /
|
||
`ServingConfig.identity` override / token override / identity-less
|
||
fail-closed), CF-006 per-call opener witness, out-of-band
|
||
`channel/close`, worker outbound calls resolving while serving,
|
||
late-registration visibility, and two finding-pinning tests
|
||
(F-1 compiler-pinned, F-2 below).
|
||
|
||
## Wrinkle probes (W1..W4) — results
|
||
|
||
- **W1 identity on the serving path — CONFIRMED 2026-09-07 against
|
||
0.6.0; RESOLVED by alkcall 0.7.0 (ledger CF-005 + CF-006).**
|
||
Original finding: `from_connection_with_serving` built channel 0
|
||
internally; its `Connection::identity()` was empty and
|
||
`ServingConfig` had no auth capture point — the serving dispatcher
|
||
resolved the caller identity ONLY from the payload `auth_token` →
|
||
`ServingConfig.identity_provider`, and the establisher/pump handler
|
||
received the install-time `AuthContext`, never the per-call opener.
|
||
**The 0.7.0 remediation (validated from this POC's topology, 16
|
||
tests):**
|
||
- `ServingConfig.identity: Option<Identity>` — the explicit
|
||
override (remediation (a)).
|
||
- The transport `Connection::identity()` (set before dialing — the
|
||
mTLS/QUIC posture) propagates to channel 0 automatically
|
||
(remediation (b)). Validated: the POC's default harness now
|
||
authorizes the scope-gated open on transport identity ALONE — no
|
||
token payload.
|
||
- Precedence (alkcall cf005 tests + POC probes): payload token >
|
||
`ServingConfig.identity` > transport identity; none → fail-closed
|
||
`FORBIDDEN` (validated).
|
||
- CF-006 corollary: the establisher/pump handler now receive the
|
||
**per-call** opener identity (validated via an identity witness —
|
||
the establisher saw `hub`, and the override identity under (a)).
|
||
On hub-forwarded opens the establisher sees the end client, not
|
||
the hub.
|
||
Status: resolved — the POC's `auth_token` workaround is dead code;
|
||
the primary path is transport identity.
|
||
- **W2 post-hoc openable registration — works.** The worker registers
|
||
its openable AFTER `from_connection_with_serving` returns; the
|
||
dispatcher reads through the shared `Arc<OperationRegistry>` per
|
||
dispatch. Proven by every open succeeding + a direct
|
||
`registration()` assertion. No finding — the assembly order
|
||
(construct client → register ops → serve) is viable as-is.
|
||
- **W3 adopter self-reaping — confirmed asymmetry, POC-local fix.**
|
||
The serving side's channel is wrapper-managed (teardown cascades on
|
||
pump completion or out-of-band close). The ADOPTING side has no
|
||
such machinery: `adopt_channel` installs routing state nothing
|
||
awaits. The hub must hold its pump handle and reap
|
||
(`teardown_channel`) itself — `ReverseTunnel::join_and_reap`/`close`
|
||
in the POC. Spec note: the real crate's consumer half needs a
|
||
session type with the same ownership (the adopted channel + pump
|
||
handle + reaper). Not an upstream gap (the hub's lifecycle is
|
||
assembly-layer by design, OQ-TN-04) — but it is the reason
|
||
`TunnelSession` in the spec must own teardown explicitly.
|
||
- **W4 EOF sentinel propagation — works both ways.** Half-close
|
||
semantics test: local `shutdown()` → EOF sentinel into the mux →
|
||
worker pump sees the zero-length read → shuts the target write
|
||
down; the target's echo still flows back before the reverse leg
|
||
completes. `pump_bidi`'s shutdown-on-completion contract holds
|
||
across the reverse path unchanged.
|
||
|
||
## Findings
|
||
|
||
### F-1 — `ChannelPlan`'s `Send + Sync` bound constrains plan payloads (compiler-pinned)
|
||
|
||
`ChannelPlan = Arc<dyn Any + Send + Sync>` (ADR-049 amendment 2).
|
||
A dialed-socket handle whose halves are
|
||
`Box<dyn AsyncRead + Send + Unpin>` does NOT satisfy the bound —
|
||
`Arc<T>` requires `T: Send + Sync`, and `dyn AsyncRead` is not
|
||
`Sync` by default. The POC widened the halves to `+ Sync` (socket
|
||
halves are `Sync`, so dial-shaped payloads are unaffected).
|
||
Implications for Phase 1: tunnel plan payloads (target handles) must
|
||
be `Sync`, which is fine for socket-backed substrates but would
|
||
exclude non-Sync handles (e.g. process pipes as boxed trait objects)
|
||
without a wrapper. **Documented upstream 2026-09-07 (alkcall 0.7.0):
|
||
the `ChannelPlan` type doc now carries the `Send + Sync` payload
|
||
constraint.** No further action needed — the bound is correct (the
|
||
plan crosses the wrapper task boundary).
|
||
|
||
### F-2 — empty UDP datagrams collide with the EOF sentinel in raw pass-through (spec-mandate)
|
||
|
||
The raw pass-through pump shape (`tokio::io::copy` between the UdpHalf
|
||
adapter and the channel) cannot carry an empty datagram: `poll_recv`
|
||
returning zero bytes is `Ok(0)` from the adapter — indistinguishable
|
||
from EOF for `copy`, which treats it as end-of-stream and shuts the
|
||
pump down. An empty datagram and the zero-length EOF sentinel are the
|
||
SAME wire shape at the pump level. Pinned by test
|
||
(`reverse_udp_empty_datagram_finding` asserts the non-round-trip so
|
||
the conclusion is executable, not anecdotal).
|
||
Consequence: the forward POC's codec decision — `[len: u16 BE]`
|
||
framing for UDP — is now validated from a SECOND angle: not only does
|
||
it preserve boundaries, it is MANDATORY for correctness (an empty
|
||
datagram is 2 bytes under the codec, unambiguous with EOF). The
|
||
Phase 1 spec must state: UDP rides the length-framed codec, never raw
|
||
pass-through. Stream substrates (TCP, unix) keep raw pass-through —
|
||
a zero-length read there is genuinely EOF.
|
||
|
||
## The `-R` registration template (SSH tcpip-forward analogue)
|
||
|
||
SSH: global request `tcpip-forward` → per-accept `forwarded-tcpip`
|
||
channel opens toward the forwarder → `cancel-tcpip-forward`.
|
||
|
||
Mapped onto the validated surface (OQ-TN-03's role-follows-resource
|
||
model — no protocol-level direction):
|
||
|
||
| SSH | alktunnels reverse shape (validated) |
|
||
| --- | --- |
|
||
| `tcpip-forward` registration | hub binds the listener locally (OQ-TN-04: binding is assembly-layer; no advertisement op needed for the hub's OWN listeners) |
|
||
| server-side accept loop | hub accept loop (assembly layer) |
|
||
| per-accept `forwarded-tcpip` open | hub calls the worker's served open op per accept (`call_with_payload`) + `adopt_channel` |
|
||
| target dial | worker's establisher dials the resource (establishment phase — typed errors) |
|
||
| `cancel-tcpip-forward` | hub closes: pump teardown + `teardown_channel` (and/or `channel/close` out-of-band on the worker) |
|
||
| originator pair (informational) | not needed — ACL rides the channels open-op machinery |
|
||
|
||
Key validation: **no advertisement surface is needed in the base
|
||
crate.** The "please listen on your side" step of SSH's template
|
||
dissolves when the listener is the hub's own local resource — the hub
|
||
does not ask the worker to listen; the worker serves open ops, and
|
||
the hub opens channels whenever its own accepts fire. An advertisement
|
||
op only becomes necessary if the LISTENER lives on the far side
|
||
("worker, expose port 8080 on your end") — which is the worker
|
||
exposing a produced resource = the forward shape with the listener as
|
||
the resource (producer-side listen is an establisher variant, not a
|
||
new mechanism). This closes the last open thread of OQ-TN-03's
|
||
original question set (the `-R` advertisement/lifecycle surface).
|
||
|
||
## What was built
|
||
|
||
```
|
||
alktunnels-reverse-poc/
|
||
Cargo.toml — alkcall 0.6.0; tokio wasm-clean subset + net (POC-local)
|
||
src/
|
||
lib.rs — module docs; wrinkle overview
|
||
producer.rs — worker half: tunnel_open_spec, tunnel_establisher
|
||
(Establishment::new(plan) — R-01; per-call
|
||
identity witness — CF-006), make_tunnel_
|
||
pump_handler (pump_bidi inline — R-02/03),
|
||
register_tunnel_openable (post-hoc, W2)
|
||
producer/
|
||
params.rs — TunnelParams {resource, substrate} + the UdpHalf
|
||
adapter (Send + Sync — F-1)
|
||
consumer.rs — hub half: open_reverse_channel (call_with_payload
|
||
with optional auth_token — now the fallback
|
||
path; primary is transport identity, 0.7.0),
|
||
ReverseTunnel::open_and_pump (adopt +
|
||
pump_bidi), join_and_reap/close (W3),
|
||
bi_stream_from_halves
|
||
harness.rs — wire()/wire_with(): worker =
|
||
from_connection_with_serving (transport identity
|
||
+ ServingConfig identity/provider — the 0.7.0
|
||
precedence chain) + ChannelOperations::
|
||
register_on + post-hoc openable; hub = adapter +
|
||
capturing install hook (manager + CallConnection)
|
||
tests/
|
||
tunnel_poc.rs — 16 tests (see Executive summary + §W1)
|
||
```
|
||
|
||
## What the POC does NOT validate
|
||
|
||
1. **A real transport** — `tokio::io::duplex` stands in for
|
||
TCP/TLS/QUIC (unchanged from the forward POC scope).
|
||
2. **The advertisement op for far-side listeners** — resolved as
|
||
"not needed" (see the template table); if a deploy mode ever wants
|
||
"worker, expose a port on YOUR side," that is a producer-side
|
||
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).
|
||
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,
|
||
fail-closed, CF-006 witness). Remaining un-exercised: fingerprint-
|
||
based identity resolution (`resolve_from_fingerprint` — the POC's
|
||
duplex transport has no TLS layer to fingerprint).
|
||
5. **Multi-hop (hub relay traversal)** — single hop; relay forwarding
|
||
(alkcall ADR-042) is transparent per leg.
|
||
6. **Wasm** — POC-local `tokio/net`; the real crate's protocol-only
|
||
core stays wasm-clean (unchanged conclusion).
|
||
|
||
## Verification
|
||
|
||
```
|
||
0.6.0 pass: 14 tests; clippy -D warnings clean; fmt clean.
|
||
0.7.0 pass: 16 tests (W1 remediation probes added, foreign-token
|
||
probe obsoleted by the precedence chain); clippy -D
|
||
warnings clean; fmt clean; repeat-run stable (3×).
|
||
```
|
||
|
||
## References
|
||
|
||
- alkcall 0.6.0 — review 007 remediation as consumed here: ADR-049
|
||
amendment 2 (`Establishment::new(plan)`, R-01), ADR-050
|
||
(`channels::pump_bidi`, R-03), R-02 JoinHandle contract.
|
||
- alkcall review 007 §Part B — the reverse-flow non-finding trace
|
||
(`from_connection_with_serving` + `register_on` + serving-side
|
||
allocation) — confirmed by execution.
|
||
- alkcall ADR-022 §2 — both-sides serving semantics (the worker's
|
||
shape). ADR-047 §5 — connection-owner allocation (the odd-ID
|
||
orientation). ADR-037/ADR-042 — channel ops, relay.
|
||
- alktunnels `docs/research/phase-0-findings.md` — OQ-TN-03 (direction
|
||
semantics — the advertisement thread this POC closes), OQ-TN-04
|
||
(no forced binding — the hub binds, the worker never does),
|
||
OQ-TN-09 (half-open residual — validated W4), OQ-TN-10 #2 (this
|
||
POC).
|
||
- alktunnels `docs/research/ssh-socks5-survey.md` §RFC 4254 §7.1 —
|
||
the `tcpip-forward` template mapped in the table above.
|
||
- forward POC (`poc-summary.md`) — the codec decision F-2 strengthens;
|
||
the UdpHalf adapter reused; the plan flow replaces `HandleHandoff`. |