Files
alktunnels/src/error.rs
T
glm-5.3-flash d586a5d2b8 feat: the forwarded op's params + the bind-first plan type (ADR-008 as amended)
- params.rs: OP_TUNNEL_FORWARDED / TUNNEL_FORWARDED_SCOPE; TunnelForwardedParams
  {listen, substrate, peer} (deny-unknown; listen positive via schema minimum 1 +
  a serde pin); tunnel_forwarded_spec() (Sub, External, alk/tunnel ALPN, scope
  tunnel:forwarded, per-substrate if/then peer sub-schema, no establisher);
  TunnelListenReply + parse_listen_reply (the additive bound/listen reply
  projection — unknown reply fields tolerated, malformed ones fail loud)
- error.rs: TunnelError::MalformedReply (the viewer's fail-loud path)
- producer.rs: BindFirstPlan {listener: Arc<dyn Any + Send + Sync>, token: u32}
  — the opaque assembly-typed listener + the correlation token; the mint
  discipline pinned in docs (non-zero-seeded AtomicU32, per-open fresh,
  per-producer unique, wraps acceptable)
- Wire-stable types pin first (the tunnels/params ordering); the establisher +
  accept dispatcher + registration land with tunnels/bindfirst-producer

Verified: cargo test (105 default; 125 --all-features), clippy --all-targets
-D warnings (native + wasm32), fmt --check, wasm32-unknown-unknown check,
cargo doc --no-deps — all clean.
2026-09-19 07:44:28 +00:00

99 lines
4.1 KiB
Rust

//! Tunnel error surfaces: the crate-level [`TunnelError`], the typed
//! session-open error ([`TunnelOpenError`], ADR-049 §4 surface), and
//! the session data-plane error ([`TunnelIoError`]).
use alkcall::channels::client::ChannelOpenError;
use alkcall::protocol::wire::CallError;
use crate::wire::DatagramCodecError;
/// The crate-level error type (thiserror; AGENTS.md convention 2).
#[derive(Debug, thiserror::Error)]
pub enum TunnelError {
/// The data-plane codec rejected an operation (UDP framing).
#[error("codec: {0}")]
Codec(String),
/// A substrate-shaped operation was attempted on the wrong
/// data plane (e.g. `send_datagram` on a stream tunnel).
#[error("wrong substrate for this operation")]
WrongSubstrate,
/// An I/O failure on the session's data plane.
#[error("io: {0}")]
Io(#[from] std::io::Error),
/// A reply field failed its typed projection — the wire carried a
/// malformed shape (e.g. [`crate::params::parse_listen_reply`]
/// found `bound` not substrate-shaped, a non-positive `listen`,
/// or a reply with no `channel_id` at all). A misbehaving
/// producer is never silently coerced.
#[error("malformed reply: {0}")]
MalformedReply(String),
}
/// A failed tunnel open or adopt. A failure never yields a session
/// (no phantom session, mirroring the no-phantom-channel property —
/// ADR-005). Branch on [`ChannelOpenError::establishment_reason`]
/// through [`TunnelOpenError::open_ref`] for `channel:open_failed`'s
/// reason (`dial_failed` / `unknown_resource` / `resource_shortage` /
/// `handler_error` / `timeout`), or the pre-establishment codes
/// (`FORBIDDEN`, `channel:too_many_channels`).
#[derive(Debug, thiserror::Error)]
pub enum TunnelOpenError {
/// The open op failed, or the reply/adopt failed — the typed
/// [`ChannelOpenError`] carries the wire [`CallError`] verbatim
/// (adopt failures surface as `AdoptFailed`).
#[error("tunnel open failed: {0}")]
Open(#[from] ChannelOpenError),
}
impl TunnelOpenError {
/// The underlying [`ChannelOpenError`] (the ADR-049 typed surface).
pub fn open_ref(&self) -> &ChannelOpenError {
match self {
TunnelOpenError::Open(e) => e,
}
}
}
/// An I/O failure on the session's data plane, or a substrate-shaped
/// operation attempted on the wrong plane (the POC's shape).
#[derive(Debug, thiserror::Error)]
pub enum TunnelIoError {
/// The underlying stream failed.
#[error("io: {0}")]
Io(#[from] std::io::Error),
/// The datagram codec rejected the operation (e.g. `Oversize` —
/// a >65535-byte send, rejected at frame time per ADR-003).
#[error("codec: {0}")]
Codec(#[from] DatagramCodecError),
/// The stream ended mid-datagram — declared length not satisfied
/// (a clean peer never does this; the fail-loud posture, OQ-TN-13
/// / ADR-003: never a silent partial datagram).
#[error("stream ended mid-datagram (truncated)")]
TruncatedDatagram,
/// A substrate-shaped operation was attempted on the wrong data
/// plane (e.g. `send_datagram` on a stream tunnel).
#[error("wrong substrate for this operation")]
WrongSubstrate,
/// The session's channel halves were already taken — by
/// `take_halves` (caller-driven) or `pump_against` (pump-owned);
/// no data plane remains on the session.
#[error("channel halves already taken from this session")]
ChannelTaken,
}
/// The reverse-path open call failed (`open_reverse_channel` — the
/// assembly layer calls it before [`crate::consumer::TunnelSession::
/// adopt`]; the hub's call surface is a `CallConnection`, so the
/// two-step is the honest API).
#[derive(Debug, thiserror::Error)]
pub enum ReverseOpenError {
/// The open op failed — the wire [`CallError`]; branch on
/// `details.reason` of `channel:open_failed` (ADR-049 §4).
#[error("open call failed: {0:?}")]
Call(CallError),
/// The open op succeeded but the reply carried no `channel_id`
/// (malformed responder).
#[error("open reply missing channel_id")]
NoChannelId,
}