- D-1: wire.md's Direct Op + Forwarded Op sections record the shipped
schema constraints (port bounds, additionalProperties: false per
branch, listen minimum: 1) and the registry-side gate ordering
- D-2: ADR-007 §2 corrected in place + a dated erratum (oneOf was
wrong-shaped; validation is registry-side, not establisher-time);
wire.md's mirrored sentence corrected
Verified: cargo doc --no-deps clean; cargo fmt --check
- BindFirstPlan gains substrate: Substrate, stamped at establisher
time from the one successful TunnelParams parse (one parse, one
source of truth; the plan-payload change is pre-consumer)
- make_bindfirst_accept_handler drops the second params re-parse and
the silent unwrap_or(Substrate::Tcp) fallback entirely — the
dispatcher reads plan.substrate; no fallback branch remains, so a
future drift cannot silently stamp Tcp into a UDP listen tunnel's
forwarded opens (the raw-vs-framed data-plane mismatch class)
- tests: forwarded_opens_carry_plan_stamped_substrate pins the plan
as the substrate's source over a real UDP bind-first wire flow
(a substrate-recording forwarded stub observes substrate: udp);
the unit downcast + establisher-flow tests pin the stamp
- docs: producer.md (the plan carries {listener, token, substrate};
the forwarded open's substrate also comes from the plan),
ADR-008 §1 (the stamp + the removed fallback)
- task review-002-m1-substrate-fallback: completed (Notes filled)
Verification: cargo test (145 pass), cargo test --all-features (153),
clippy --all-targets -D warnings (default + all-features), fmt
--check, doc --no-deps, wasm32 check + clippy -D warnings.
Verification pass against the landed alkcall 0.8.0 surface surfaced one
design gap: ADR-008 §3 pinned the forwarded op's correlation key as the
listen channel's ID, but (1) the serving-side OpenHandler/establisher
surface never exposes the allocated channel ID, and (2) the in-tree
generic ChannelRelay forwards open-op params untouched (it rewrites
only the reply's channel_id), so a spoke's forwarded open would carry
an id the end opener never allocated. Amended (wire still unconsumed —
alksocks is docs-only — so pre-consumer and legal):
- ADR-008 Amendment 1: the bind-first establisher mints a fresh u32
correlation token per open — carried in the plan (for the accept
dispatcher) and contributed as the additive `listen` reply field
(the same projection carrier `bound` rides); the forwarded op's
params rename `channel` -> `listen` with token semantics. Tokens
ride reply fields + params through hubs untouched (relay-transparent
by construction, alkcall ADR-051). Consumer-minted tokens and a new
upstream handler surface were considered and rejected (rationale in
the amendment).
- Spec docs synced: wire.md (the `bound`+`listen` reply section, the
forwarded params), producer.md (bind-first shape + forwarded op),
consumer.md (TunnelListener gains `listen()`; correlation registry
posture), overview.md, OQ ledger (OQ-TN-16 updated),
tunnels-graduation.md.
Task decomposition (tasks/tunnels/, Phase 2 of the graduation — the
alkcall prerequisites are gone):
- direct-op — params/spec/establisher/open_direct (ADR-007)
- bindfirst-params — forwarded-op spec, TunnelListenReply, the
BindFirstPlan + token mint (wire types first, the params.md
ordering precedent)
- bindfirst-producer — bind-first establisher, accept dispatcher,
register_tunnel_bindfirst_openable
- forwarded-receipt — the opener-side serving op + reply-field reader
- tunnel-listener — the listen-flow consumer session (ADR-008 §4)
- local-bind-halves — the `local` TCP bind + accept-pairs loop,
unix parity decision, direct-op DialFn reuse pin
Dependency order: direct-op and bindfirst-params are independent
roots; bindfirst-producer -> {bindfirst-params}; forwarded-receipt ->
bindfirst-params; tunnel-listener -> {bindfirst-params,
forwarded-receipt}; local-bind-halves -> bindfirst-producer.
Verification: cargo test 71 passed; clippy --all-targets -D warnings;
fmt --check; wasm32-unknown-unknown check + clippy clean (docs + task
files only — no code change).
The alksocks graduation research (tunnels-graduation.md) is decided;
the ADRs are normative now:
- ADR-007: the direct open op — channels/tunnel/direct on the one
alk/tunnel ALPN, params {substrate, target} (self-describing JSON
target; ATYP bytes rejected as not schema-describable), scope
tunnel:direct (never implied by tunnel:open), dial-only.
- ADR-008: listen metadata — bind-first listen establisher (reply
gains additive 'bound' = BND.ADDR fidelity; the BIND deadlock +
10s-bound findings dissolve) + the forwarded op
channels/tunnel/forwarded (accept-as-open, params carry
{channel, substrate, peer} = reply#2 fidelity). Accept-first stays.
- ADR-001/002/006 amendments: params layout is per-op; flavor-form
op ids on the one ALPN; per-flavor scopes + the hub re-produce
policy sentence (the hub's ACL governs the final dial).
- wire/producer/consumer/overview/README updated for the new ops,
the three establisher shapes, TunnelListener, open_direct.
- OQ-TN-15/16 promoted + resolved; upstream alkcall asks filed at
alkcall/docs/reviews/008-tunnels-graduation-upstream-review.md
(flavor-form discovery derivation; the establisher → reply
projection).
Verification: cargo test (22 passed), clippy -D warnings, fmt --check
(docs-only change; wasm check unaffected)
Downstream alksocks motivation: CONNECT/BIND/ASSOCIATE need three
additions, all generally useful:
- One wire addition: a direct/dynamic open op on the alk/tunnel ALPN
(channels/tunnel/direct, scope tunnel:direct) — the ssh
direct-tcpip capability, additive to ADR-001's resource-not-address
params. Options weighed; second-op lean (ACL separation at the op,
per the socks5:bind scope-gate precedent).
- The two BIND metadata asks (bound-addr discovery, peer metadata)
with carrier options, including the forwarded-tcpip alternative
(reverse open with peer/target in params) that retires both with
existing reverse-path machinery.
- Callback→provider mapping: the composed providers the graduation
gives each alksocks seam (DialFn/EgressFactory/BindAcceptFactory)
next to the local-socket ones.
Also records the vpn-like endgame positioning (virtual-subnet
translation via dial callback, signing as directory/identity-layer)
and alknodes as the OQ-TN-12 re-produce consumer.
Verification: docs-only change (cargo test/clippy/fmt unaffected).
Review 001 N-3 (tasks/tunnels/decide-take-framed-halves), decided
before the first external consumer:
- Option 1 (keep as-is, docs carry it), recorded as ADR-005
§Amendment. No external consumer exists (window open — the breaking
reshape was available but undemonstrated); one take_halves semantic
across substrates beats per-substrate divergence; pump_against
composes the framed adapter (UdpHalf), not framed halves; and the
take_framed_halves convenience is additive — it can land after the
first consumer defines its shape.
- consumer.md's datagram section states the posture; review 001 §N-3
resolution note landed; task marked completed.
Docs-only (no code change). Verified: fmt --check, doc --no-deps
warning-free, 63 default / 76 local tests green.
Review 001 remediation (tasks/tunnels/fix-udp-framed-adapter):
- U-1 [major]: UdpHalf now composes the ADR-003 codec at the
substrate boundary — poll_write de-frames channel bytes (incremental
DatagramReader) and sends each payload as one raw datagram;
poll_read frames raw target datagrams and hands frame bytes to the
pump. Targets see payload-only bytes; empty datagrams round-trip
(the F-2 fix end-to-end); >8 KiB datagrams arrive as ONE datagram.
Mechanics the verification gates forced beyond the sketch:
poll_write accepts all offered bytes (tokio copy re-offers a full
window unsatisfied — partial-frame holds deadlock >8 KiB frames),
backpressure is an accepted-byte high-water mark, socket drain
pumps on write/flush/shutdown; >caller-buffer frames stage across
reads (the truncation probe's assertion surface updated per the
re-validation correction).
- U-2 [minor]: connect_udp resolves the target and binds the
unspecified address of the matching family (v6 targets dial from
[::]:0); bind_tcp gains the wildcard-exposure doc warning.
- U-3 [minor]: the 65535 recv scratch allocates lazily on first recv
(full-size read kept — try_recv truncates silently; the MTU-shrink
idea confirmed unsafe in re-validation).
- N-1: the module/connect_udp/UdpHalf docs now describe the composed
codec truthfully.
Gates (permanent tests in tests/local_halves.rs): recording-target
suite (payload-only at the target, empty-datagram round-trip, 9000-
byte single datagram, unframed-target resolution), the 65507-byte
max-datagram staging probe, and the v6 dial gate.
Docs: ADR-003 §Amendment (the boundary mechanics), wire.md truncation
bullet, consumer.md adopt sketch (substrate param). Review 001
resolution notes on U-1/U-2/U-3/N-1; task marked completed.
Verified: 63 default / 76 local tests ×3 repeat runs; clippy
-D warnings (all-targets + wasm32), fmt --check, doc --no-deps
(warning-free), wasm check, publish dry-run.
- All findings re-validated (fresh executable probes for U-1 over
real sockets — all four behaviors reproduced; fixed-in-review
items confirmed in-tree; battery green 63/70).
- U-1 amendment: the review's fix sketch was direction-inverted
(frame-on-write describes the TARGET face; UdpHalf's caller is the
pump, so poll_write DE-frames channel bytes and poll_read FRAMES
target datagrams). Gate 4 and U-3 corrected: the truncation test's
assertion surface may change; the 65535 recv scratch is
correctness-load-bearing (tokio try_recv truncates silently).
- Task fix-udp-framed-adapter updated to the corrected mechanics.
- New task decide-take-framed-halves (N-3): the datagram
take_halves API posture, to decide before the first external
consumer.
Verification: cargo test 63/70 both configs, clippy -D warnings
(all-targets + wasm32), fmt, cargo doc --no-deps (0 warnings),
publish dry-run, taskgraph validate (15 tasks, acyclic).
Sharpened from 'v6 blind spot' into a general bind-posture rule after
follow-up discussion: no implicit wildcard binds (0.0.0.0 / :: — note
::1 is loopback, not a wildcard); connect_udp is a client socket so
the floor fix is family-correct binding, never a loopback default;
bind_tcp keeps its caller-supplied address (the correct posture) with
a wildcard-bind doc warning. Executable rider sharpened on the
remediation task.
Findings + review doc: docs/reviews/001-implementation-review.md (the
alkcall house pattern). Closes tunnels/review-core-crates and
tunnels/review-impl.
Findings:
- U-1 [major, NOT fixed] — the local UDP adapter is not the framed
adapter ADR-003 mandates: codec-framed bytes reach real targets,
empty datagrams are EOF-shaped at the adapter (tunnel teardown),
>8 KiB datagrams split, unframed target-initiated datagrams are
dropped. Executable-pinned by real-socket probes (temporary test,
deleted after the run). Remediation task: tunnels/fix-udp-framed-adapter.
- C-1 [major, fixed] — AcceptQueue::pop lost wakeup in the
check→register window (open op hangs to the establishment bound);
fixed via the tokio notify_waiters contract (Notified created before
the check each iteration) + multi-thread regression test.
- N-4/N-5/N-7/N-8/N-9/N-10/N-11 [minor, fixed] — serialize TunnelParams
in open_reverse_channel; stale doc ref; broken doc link; root
re-exports (TunnelParams/Substrate/tunnel_open_spec/OP_TUNNEL_OPEN);
README (publish dry-run was failing on the missing readme); harness
comment; pump_against UDP framing doc note.
- N-2 retracted by executable falsification (codec buffering is
bounded by construction); N-6 non-finding with rationale.
Verified: 63 tests default / 70 with --features local, 3x repeat-run
clean both configs; clippy -D warnings (all-targets + wasm32); fmt;
doc warning-free; wasm32 check; cargo publish --dry-run passes.
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)
The Phase 0 OQ ledger (OQ-TN-01..10) promoted into
docs/architecture/open-questions.md (now OQ-TN-01..14, with four
new Phase 1 residues). Six ADRs and four spec docs, all Draft, all
decision-shaped per the SDD process (ADRs carry the WHY; specs carry
the WHAT and reference by number):
ADRs:
- 001 open-params-layout: params = {resource, substrate} — the
producer's stable name, not an address; wire-stable from the first
consumer; unknown substrates fail loudly at schema time
- 002 alpn-strategy: single alk/tunnel ALPN (option A); substrate in
params selects the framing; prior art (SSH/SOCKS5/udpgw) gives no
reason for the split
- 003 codec-and-udp-framing: raw pass-through (stream) / mandatory
[len: u16 BE] (UDP) — F-2 mandate recorded (the codec is mandatory
for correctness, not cosmetics); len=0 = legal empty datagram;
OQ-TN-13 resolved fail-loud (truncation)
- 004 no-backend-trait: halves functions at the assembly layer;
listen is an establisher shape; hub re-produce deferred(OQ-TN-12);
trait re-evaluated at the alknet ADR-078 convergence threshold
- 005 consumer-session-owns-teardown: TunnelSession with
open/adopt/stream_halves/take_halves/pump_against/close/join/Drop —
the W3 adopter gap closes structurally; Drop is the best-effort
fallback
- 006 access-control-posture: the open gate is the boundary;
TUNNEL_OPEN_SCOPE = tunnel:open (stable once published); op-level
ACL; ownership seam; no allowlists in v1; identity = 0.7.0's
precedence chain
Spec docs:
- overview.md: purpose, resource model, deps (alkcall 0.7.0,
wasm-clean default, local feature), module map
- wire.md: the open op (params/reply/typed errors), the data plane
by substrate, sentinels + half-close, byte diagrams
- producer.md: spec, establisher (dial + listen shapes), pump
handler (pump_bidi inline, R-02), registration API, ACL posture
- consumer.md: TunnelSession (forward open + reverse adopt
construction), data plane, teardown API (close/join/Drop incl.
pump-less join semantics)
- bast.md: the BAST doc for the UDP codec (convention 12's trigger
fired — the framing IS binary beyond pass-through)
- open-questions.md: OQ-TN-01..14; 01..10 promoted (faithful to the
phase-0 ledger's final states), 11 partially resolved (collision
domain = per-producer registry per ADR-001; lifecycle open),
12 deferred(scope), 13 resolved fail-loud (ADR-003), 14 open
Verified by an architecture-reviewer pass (2 criticals, 6 majors,
10 minors — all fixed: ADR-001/003-vs-OQ decision-state contradictions
resolved; the Layer-2 mislabel corrected to the ADR-047 §4 per-session
fork; alktty/alknet ADR misattributions fixed; codec placement pinned
to the establisher (pump stays substrate-agnostic); reverse-path
construction named (TunnelSession::adopt + pump_against); security
posture promoted to ADR-006; BAST doc written; F-2 rationale deduped;
README tables completed; channel_id > 0; MTU wording fixed; ADR-047 §5
attributions corrected; impacts lines added to unresolved OQs; review/
ledger pointer paths added to README references).
Verification: doc set internal cross-refs all resolve; cargo test,
fmt --check, doc --no-deps clean
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
POC at /workspace/alktunnels-reverse-poc (14 tests, clippy/fmt clean,
repeat-run stable) over alkcall 0.6.0:
- The review 007 non-finding trace confirmed by execution: the hub
(accept side) opens tunnel channels toward a connect-side worker
serving its own open op (from_connection_with_serving +
ChannelOperations::register_on + post-hoc openable registration)
- ADR-047 §5 exercised in the Pub-like orientation: the worker
(connect side) allocates odd IDs; the hub adopts
- R-01 plan flow under concurrency: two same-resource opens, distinct
channels, distinct dialed handles — the forward POC's handoff race
is structurally gone
- OQ-TN-03's last open thread closed: no advertisement op needed —
the listener is the initiator's own local resource; a far-side
listener is a producer-side listen establisher over the same open op
- W4 EOF sentinel propagation validated (half-close semantics);
W3 adopter self-reaping pinned as a TunnelSession spec requirement;
W2 post-hoc registration viable
Findings:
- F-1: ChannelPlan's Send+Sync bound constrains plan payloads
(socket halves carry + Sync; spec should pin the constraint)
- F-2: empty UDP datagrams collide with the EOF sentinel in raw
pass-through (pinned by an executable test) — the length-framed
codec is mandatory for UDP; raw pass-through stays
stream-substrate-only. Second validation of the codec decision;
OQ-TN-02 mandate strengthened
W1 (connect-side serving path resolves caller identity only via
payload auth_token) filed upstream as alkcall consumer-findings
ledger CF-005 (alkcall 0d287a9).
Verification: cargo test 14/14, clippy --all-targets -D warnings,
fmt --check — all clean, repeat-run stable
17 tests pass (7 codec + 10 integration). The producer/consumer
shape, ADR-049 establishment integration, codec decision, and typed
establishment errors all validated over alkcall 0.5.0 channels.
Key findings folded into the ledger:
- OQ-TN-05 resolved for stream substrates: no backend trait —
pump_halves generic over boxed halves (TCP into_split, UDP
UdpHalf adapter); remaining thread is only the hub re-produce
composition question
- OQ-TN-06 convergence test satisfied: producer pump and consumer
pump are the same shape modulo channel side; extraction decidable
in Phase 1
- New finding for the alkcall ledger: OpenHandler JoinHandle
semantics are load-bearing and undocumented — a handler that
returns before the data plane starts tears the channel down at
birth (the POC found this by hanging; the pump must be awaited
inline). Plus: Establishment should carry the channel plan
(the reserved extension point) to kill the establisher handoff
side-channel
- UdpHalf truncation semantics flagged for the Phase 1 ADR
(recommend fail-loud over silent truncation)
POC crate: /workspace/alktunnels-udp-poc (db224cc).
Structural rationale: alktty's 5-byte header is a sub-demux key for
five logical streams sharing one BiStream; a tunnel has exactly one
data stream per direction, so no type byte. TCP needs no length
prefix (channels already length-prefixes every chunk). UDP needs
boundary re-framing only, and u16 suffices (UDP max payload 65507).
Wire overhead table: tunnel TCP = 8 B/chunk total (channels header
only), tunnel UDP = 10 B/datagram, vs alktty's 13 B. udpgw
calibration: 5 B + per-datagram address.
Trade-offs accepted: no in-band control path for TCP channels ever
(post-ADR-049 this is clean; escape hatch = new ALPN, not a wire
change); sentinel collision does not exist (UDP len=0 = empty
datagram, legal; EOF is the channels-level sentinel on the BiStream
at a different layer); future multi-endpoint UDP gateway framing
lives inside datagram payloads (udpgw precedent).
Residual for the Phase 1 codec ADR: empty-datagram semantics; u16 BE
over varint (leaned, MTU-bounded anyway).
Research specialist survey (docs/research/ssh-socks5-survey.md):
SSH forwarding model (direct-tcpip/forwarded-tcpip payloads, open-
failure reason codes, tcpip-forward registration lifecycle), SOCKS5
(ATYP, CONNECT, UDP ASSOCIATE), error-vocabulary comparison table,
endpoint-at-open vs per-datagram analysis, and an explicit anti-
prior-art list (SSH window updates, packet-size negotiation, dual
channel numbering, SOCKS5 FRAG/auth/BND semantics, udpgw
KEEPALIVE/pooling — all mechanisms channels already owns or the
substrate change obsoletes).
Key corrections to phase-0 findings:
- OQ-TN-02 category error fixed: SSH has NO UDP forwarding at all;
the earlier '-D UDP associate over SSH' line was wrong (SSH -D
carries only the SOCKS5 TCP control connection). Real UDP-over-
stream prior art: tun2proxy udpgw + SOCKS5's own UDP relay.
- OpenSSH -D is protocol-level indistinguishable from -L (one
direct-tcpip channel per SOCKS CONNECT) — direct confirmation of
the -D = 'tunnel a socks5 connection' resolution.
OQ ledger updates:
- OQ-TN-01: minimal params shape supported ({resource, substrate});
direct-streamlocal is the extensibility template; ATYP not needed
in base params
- OQ-TN-02: RESOLVED as split by path — endpoint-at-open for base UDP
resources; per-datagram addressing only inside the -D payload;
channel ID replaces CONN_ID (no second demux); KEEPALIVE drops
- OQ-TN-07: option A (single alk/tunnel ALPN) strengthened
- OQ-TN-09: reason-code vocabulary from SSH's four codes + detail
string; udpgw ERR bit flagged as the counterexample to avoid
- OQ-TN-10: reverse-flow POC gains the tcpip-forward template
- Checklist: SSH/SOCKS5 survey item checked off
Worktree/branch for repo-dependent POCs; standalone crate in the
global workspace for self-contained POCs (alknet-channels-poc
precedent); findings always land in docs/research/.
- OQ-TN-08 residue updated with the upstream posture: we own alkcall,
upstream asks happen early (alktty/alkhttp precedent — both required
upstream fixes/additions; only 3 downstream dependents now, each
resolved issue makes future dependents cheaper). Ops-listing
metadata is expected to be a small upstream alkcall change, not a
workaround.
- OQ-TN-09: control-frame hunch accepted (self-contained frame,
alktty ADR-006 shape; dial errors are channel-closing, byte EOFs
stay per-direction); frame vocabulary follows the alktty pattern;
concrete set = Phase 1 ADR.
- New prior art section: the alktty stream-splitting pattern
(split BiStream -> ChunkReader/ChunkWriter -> pump to handle halves)
proposed as the general tunnel pattern — 'tty without the five
stream types'. Comparison table (tty vs TCP vs UDP codecs/handles/
pumps); implications for OQ-TN-09 (ctrl frames via stream types),
OQ-TN-05 (handle = boxed AsyncRead/AsyncWrite halves; UDP flow
table is the real design surface), OQ-TN-06 (helper convergence
test against pump_session may be nearly free). Open: codec choice
(reuse 5-byte / stripped / raw + UDP framing).
- Checklist updated accordingly.
Untangles the 'host owns the resource and proxies on top' posture for
tunnels:
- Protocol-crate levels assume the resource is owned by the other side
of the connection; alkcall's existing op-level ACL applies as-is. No
new policy layer, target allowlists, or tunnel-specific ownership
machinery. Proxy/overlay is a downstream (assembly-layer) concern
(hub workers expose tunnels; hub overlays them per-ACL).
- Discovery resolved: openable channels are operations (alkcall
ADR-047), so the existing bidirectional ACL-filtered ops listing IS
tunnel-resource discovery. A consumer learns a socks5/postgres/redis
tunnel is available from the same listing it already uses for ops.
- -D simplifies to 'just tunnel a socks5 connection': socks5 server
lives on the producing side; target selection happens in the socks5
protocol, not tunnel params; the dynamic-target policy residue
dissolves (socks5 resource ACL governs reachability).
Residue for Phase 1: per-resource metadata in the ops listing
(substrate type, name/description) — an alkcall ADR-047 interaction,
not a new mechanism. OQ-TN-01 discovery residue and the hub-model
residue list cross-linked accordingly.
Four corrections from discussion:
- ALPN prefix: alknet/ -> alk/ swap happened in alkcall v0.1.1; docs
must not perpetuate the old prefix (consumers would bake it in)
- XY problem: except in the -D/dynamic composition case, params only
identify a produced resource + substrate discriminator (tcp/udp/
extensible); the producer owns where the resource comes from (most
likely a local port); UDP must be structurally supported even
though TCP dominates expected use
- self-contained JSON open-op object accepted as the params path
(alktty NegotiateRequest precedent)
- russh has no UDP channel support at all — no direct-udp prior art;
noted in OQ-TN-02 and the survey list; SOCKS5 relevance scoped to
the -D composition path
Residue for OQ-TN-01: resource naming shape, discovery mechanism,
exact JSON layout (Phase 1 ADR before first consumer).
Captures the standing hub stance as prior art: the hub owns the
connection and explicitly proxies to expose resources for others.
Role follows the resource — whoever reaches the target is the producer
(registers openable channels), whoever wants bytes is the consumer.
SSH -L/-R collapse to the same producer/consumer pair with the entry
point on different machines (assembly-layer wiring); the exposed port
is a virtual ACL-scoped resource (register_openable shape), not a
bind; the hub proxy is a producer wrapping a consumer, terminating
and re-producing per hop so ACL applies per hop (distinct from
ADR-042 transparent relay); -D composes as a consumer opening
channels with per-connection dynamic targets.
OQ-TN-03 resolved. OQ-TN-04 mostly resolved (binding always
assembly-layer, optional, either side). OQ-TN-01 reframed: params
name a produced resource — resource naming + discovery added. OQ-TN-08
strengthened: register_openable per-resource registration is the
primary gate; remaining residue is the dynamic-target policy hook.
OQ-TN-05 gains the hub re-produce composition sub-question. OQ-TN-02
cross-linked: endpoint-at-open aligns with resource naming; per-
datagram addressing matches the -D composition path.
Documents the previously-undocumented UDP gateway prior art discussed
with the alknet-channels POC agent (/workspace/tun2proxy/src/udpgw.rs):
per-datagram length framing over a stream (LEN|FLAGS|CONN_ID|[SOCKS5
addr]|DATA), SOCKS5 ATYP addressing in-band, CONN_ID flow multiplexing,
keepalive/ERR flag packets, MTU cap, idle expiry. Folds implications
into OQ-TN-01 (addressing fork: per-channel vs per-datagram), OQ-TN-02
(framing mechanics production-proven; remaining fork documented),
OQ-TN-07 (single-ALPN option strengthened), OQ-TN-09 (flag-packet
vocabulary maps to establishment/error frame question), OQ-TN-10 (UDP
POC scope narrowed to channels-layer fit), and the survey checklist.
Also records the transport story: TCP vs QUIC is the alkcall layer's
concern; the tunnel crate is transport-agnostic.
Captures the ten open design questions from the setup discussion:
addressing format, UDP datagram semantics, -L/-R/-D direction model,
no-forced-binding API surface, backend inversion point, two-pump
helper extraction, ALPN strategy, access control scope, lifecycle/
error reporting, and candidate targeted POCs. Includes the settled
foundation (POC-validated prior art), a prior-art survey list, and
the Phase 0 convergence checklist.