chore: consume alkcall 0.5.0; OQ-TN-09 resolved by ADR-049

alkcall 0.5.0 landed the review 006 remediation (filed from this
crate's Phase 0 pass):
- E-01/N-1: channel-open establishment phase (ADR-049) —
  register_openable_with_establisher, bounded await, teardown-on-
  failure, typed channel:open_failed reply (reason in details);
  ChannelClient::open_channel returns ChannelOpenError
- E-02: OperationSpec.description disclosed via discovery
- E-03/E-04/N-2: teardown-race log, early-arrival bound docs,
  count accessor

alktunnels updates:
- Cargo.toml: alkcall 0.4.0 -> 0.5.0 (lockfile resolves 0.5.0)
- AGENTS.md convention 11: pin note updated; ADR-049 marked
  load-bearing (tunnel opens use register_openable_with_establisher)
- phase-0-findings OQ-TN-09: RESOLVED by ADR-049 — the in-band
  control-frame hunch is superseded (establishment is the open op's
  reply, not a stream frame); residual for Phase 1 narrowed to
  half-open semantics; v1 needs no mid-stream control frame
- phase-0-findings OQ-TN-08: per-op metadata residue resolved by
  E-02 (description field; OQ-40 enumerate half stays deferred)
- tun2proxy prior-art + alktty-pattern sections: frame-vocabulary
  notes updated to point at the ADR-049 supersession
- upstream posture paragraph updated: review 006 is the working
  proof of the ask-early rule

Verification: cargo test (0 tests, ok), clippy --all-targets
-D warnings, fmt --check, wasm32 check, doc --no-deps — all clean
This commit is contained in:
2026-09-06 20:05:41 +00:00
parent 1a3eb8418d
commit d88e148fdc
4 changed files with 91 additions and 59 deletions
+81 -53
View File
@@ -170,9 +170,12 @@ documented. Key mechanics, all of which generalize:
prior art, not speculation — raises confidence in the OQ-TN-02
half-answer considerably.
- The frame-type set (DATA/KEEPALIVE/ERR) is a useful minimal
vocabulary — it maps onto OQ-TN-09's establishment/error frame
question (tun2proxy uses flag packets, alktty uses typed control
chunks; both are self-contained frames inside the data stream).
vocabulary — historically mapped onto OQ-TN-09's establishment
question, now superseded there by alkcall ADR-049 (establishment
is the open op's reply, not an in-stream frame). KEEPALIVE and
ERR both drop (channels owns liveness; establishment is
call-level). The vocabulary survives only as anti-prior-art
context.
- Per-datagram addressing (SOCKS5-style) vs per-channel addressing
(fixed target at open) is a real fork for the params design: TCP
tunnels fix the target at open; UDP associations may either fix
@@ -208,12 +211,13 @@ far-end handle:
Implications:
- **OQ-TN-09 falls out naturally.** alktty's `STREAM_CTRL_IN/OUT`
(flag frames inside the same codec) is the precedent for the
establishment/error frame: same codec, one extra stream type
(`TUNNEL_CTRL` or similar), typed control messages — rather than a
separate framing layer. alktty's zero-length-sentinel EOF semantics
carry over unchanged.
- **OQ-TN-09: partially superseded by alkcall ADR-049.** The
establishment/error half is resolved at the call layer (the open op
replies `channel:open_failed`; no in-stream establishment frame).
What the ctrl-frame pattern still informs: whether a *mid-stream*
control frame is ever needed (v1 likely not — see OQ-TN-09's
resolved status), and the alktty zero-length-sentinel EOF semantics
carry over unchanged either way.
- **OQ-TN-05 (backend trait) gets a concrete shape to judge.** If
tunnels are "split + wrap + pump," the far-end handle is just
"(AsyncRead + AsyncWrite) halves" for stream substrates — which is
@@ -571,21 +575,21 @@ the same posture alktty already uses:
the socks5 resource governs everything reachable through it, plus
whatever policy the socks5 implementation itself applies downstream.
Residue for Phase 1 (spec-shape, not research): confirm the
ops-listing surface carries enough per-resource metadata (substrate
type, resource name/description) for a consumer UI to distinguish
produced tunnels — or whether `params`-describing metadata rides
alongside the operation spec. That is an alkcall ADR-047 interaction,
not a new mechanism.
Residue for Phase 1 (spec-shape, not research): **resolved 2026-09-06
by alkcall 0.5.0** (review 006 E-02): `OperationSpec.description`
(`with_description`) lands additively and round-trips through
`services/schema` / `from_call` / `op/register`, disclosed by
`services/list` and `services/list-peers` when set. The tunnel open op
carries its description; the live resource-enumeration half (OQ-40)
stays deferred and is not needed for v1.
**Upstream posture (2026-09-05):** we own the upstream. Both prior
downstream crates (alktty, alkhttp — `/workspace/@alkdev/alkhttp`)
required fixes/additions to alkcall, and the rule is to make those
asks *early*: there are only three downstream dependents right now,
and each issue resolved upstream makes the next dependent cheaper. So
if the ops listing needs per-resource metadata (or anything else in
the ADR-047 interaction), the expectation is a small upstream alkcall
change in Phase 0/1 — not a workaround in this crate.
**Upstream posture (2026-09-05, updated 2026-09-06):** we own the
upstream, and the rule is to make asks early. The E-01/E-02 sweep
(alkcall review 006, filed from this crate's Phase 0) is the working
proof: the establishment phase (ADR-049), the typed
`ChannelOpenError`, and `OperationSpec.description` all landed in
0.5.0 within a day of being filed — alktunnels was the consumer that
pulled them through. Future upstream asks follow the same path.
### OQ-TN-09: Lifecycle, teardown, and error reporting
@@ -604,29 +608,48 @@ POC issue #6). What's missing is the error/level above bytes:
(standard two-pump behavior) — is that always desired, or does the
consumer need a "close both" control?
**Status: direction set 2026-09-05; vocabulary informed 2026-09-06**
(survey `ssh-socks5-survey.md` §Comparison). The original half-answer
is accepted: a self-contained control frame (alktty ADR-006 shape)
carrying an establishment result/error, sent before any data chunk;
dial errors are tunnel-closing (the whole channel dies), whereas
byte-level EOFs stay per-direction. The alktty-style JSON frame is the
strictly richest of the four prior-art vocabularies (SSH code+desc,
SOCKS5 codes-only, udpgw 1 opaque bit — the counterexample to avoid).
**Status: resolved 2026-09-06 by alkcall ADR-049** (alkcall 0.5.0,
review 006 E-01 — the establishment gap filed from this crate's Phase
0 pass). The in-band control-frame hunch below is **superseded**:
establishment failure is now the open op's reply, not a frame on the
channel stream.
Survey inputs for the Phase 1 frame ADR:
- Content: establishment ack + failure frame with a small reason-code
set mirroring SSH's four (policy-denied / dial-failed /
unknown-resource-or-substrate / resource-shortage — mapping 1:1 onto
what an open handler can produce: ACL denial, dial failure,
unregistered resource/substrate, channels limits) + a detail string
(SOCKS5's network/host/refused granularity lives in `detail`, not
more codes — codes are wire-stable, detail strings are not).
- Failure ⇒ channel closes before data flows: all four prior arts
agree; there is no "fail the open but keep the channel" prior art,
and none is needed (consumer just opens a new channel).
- The frame does NOT need: window adjustments (channels owns
backpressure), keepalives (channels owns liveness), fragmentation
(not the tunnel's job), per-datagram error replies.
- The producer's tunnel open op registers via
`ChannelCore::register_openable_with_establisher`; the establisher
(`OpenEstablisher`) runs as an awaited, bounded establishment phase
— semantically validate params, **dial the target** — before the
open op replies. On failure the just-allocated channel is torn down
(allocation and teardown balance; ledger un-increment) and the
consumer receives `channel:open_failed` with
`details: {reason, message}`, `reason ∈ dial_failed /
unknown_resource / resource_shortage / handler_error / timeout`
the SSH contract consumer-visibly (a failed open never returns a
`channel_id`). No phantom channel, no establishment frame needed on
the data stream.
- Implementation note (ADR-049 amendment): the establisher takes
`(input, auth)` only — the channel's yield-once `BiStream` belongs
exclusively to the pump handler. The tunnel establisher hands the
dialed connection to the pump handler through its own path (e.g. a
oneshot/`Arc<Mutex<Option<_>>>`), not via the open-op input.
- What remains for this crate's Phase 1 ADR (narrowed from the
original frame-vocabulary question):
- Half-open semantics: one direction EOFs, the other keeps pumping
(standard two-pump behavior) — the only establishment/teardown
question not answered upstream. Dial errors are fully
establishment-phase now; byte-level EOFs stay per-direction
(unchanged).
- Whether the tunnel ever needs a *mid-stream* control frame
(post-establishment). Per ADR-049 §6's pinned posture,
pump-phase failures are EOF-shaped by design; the SSH/SOCKS5
survey found no prior art for in-stream control after
establishment beyond udpgw's KEEPALIVE (dropped — channels owns
liveness). v1 likely needs none; if a UDP flow table ever needs
in-band signaling, that is a substrate-framing decision, not a
base-wire one.
Original question (retained for context): how does a failed target
dial reach the consumer, is there an establishment ack before byte
pumping, and what are the half-open semantics?
### OQ-TN-10: POC scope for what remains unvalidated
@@ -719,14 +742,19 @@ Candidate reading for the research specialist (to be expanded):
ownership assumed on the far side; alkcall's existing ACL
applies as-is; ops listing (ADR-047) is tunnel-resource
discovery; `-D` = "tunnel a socks5 connection" (target selection
in the socks5 protocol, not params). Residue: per-resource
metadata in the ops listing — small upstream alkcall ask,
early per the upstream posture (alktty/alkhttp precedent)
- [x] Lifecycle/error direction set (OQ-TN-09) — establishment/error
control frame accepted in principle (dial errors are
channel-closing); frame vocabulary follows the alktty
stream-splitting pattern; concrete set = Phase 1 ADR
- [ ] Codec decision (new, from the alktty pattern prior art) — reuse
in the socks5 protocol, not params). Per-op metadata residue
RESOLVED 2026-09-06: alkcall 0.5.0 review 006 E-02 lands
`OperationSpec.description` (round-trips through discovery);
the live resource-enumeration half (OQ-40) stays deferred and
is not needed for v1
- [x] Lifecycle/error resolved (OQ-TN-09) — superseded 2026-09-06 by
alkcall ADR-049 (0.5.0): establishment is the open op's awaited
phase (`register_openable_with_establisher`); dial failure is a
typed `channel:open_failed` call error (reason ∈ dial_failed /
unknown_resource / resource_shortage / handler_error / timeout),
never a phantom channel. Residual for Phase 1: half-open
semantics only; v1 needs no mid-stream control frame
- [ ] Codec decision (from the alktty pattern prior art) — reuse
5-byte format vs stripped variant vs raw pass-through + UDP
framing; feeds OQ-TN-07 (ALPN) and the OQ-TN-05 trait shape
- [ ] Decision input: backend trait vs no-trait (OQ-TN-05), now