assembly: the cross-cutting integration suite + docs sync (batch 2 closes)

- tests/assembly-integration.rs (7 tests, local + composed): the full
  -D story (vanilla cred-bearing client -> TCP door + UDP door ->
  producer w/ local providers -> real echo targets; CONNECT + BIND +
  ASSOCIATE live, one-command-per-connection, teardown + fresh
  sessions), the composed story (direct_egress + bindfirst_accept +
  real TCP dial; all three commands; the lazy-table dial-count
  witness), mixed assembly (channels-native + door clients on one
  registration; composed seams beside native sessions), the assembly
  teardown matrix (UDP-door idle bound; channel reaped; fresh
  association after), the producer door driving all three commands,
  and the -D domain-target pin (resolution at the producer's seam,
  ADR-012).
- Fix the drift the suite surfaced: ComposedEgress::next_reply ended
  fresh (never-opened-table) associations at birth — the ended-relay
  Err now fires only once a tunnel existed and every relay ended; the
  fresh table parks (the readiness contract). Regression-pinned in
  tests/composed-egress.rs (fresh_association_parks_before_any_
  tunnel_is_opened).
- Harness: the composed-accept machinery single-sourced into
  tests/harness.rs (the composed suites' shapes + the five-gate
  composed identity); composed-accept.rs consumes the shared shapes.
- Docs sync: README Current State (the assembly layer landed; both
  suites named), lib.rs crate docs (the assembly-layer paragraph),
  consumer.md (the TokenClient door-only contract + the
  one-command-per-connection posture reconciled).
- Task assembly-integration-suite: completed; Notes + Summary filled
  (the local-on-wasm failure observed: mio's unsupported-wasm error).

Verification: cargo test (375), cargo test --all-features (615),
clippy --all-targets -D warnings (default + all-features), fmt --check,
doc --no-deps (0 warnings), both wasm checks (default + composed),
local-on-wasm fails as designed.
This commit is contained in:
glm-5.3-flash committed 2026-09-25 23:03:13 +00:00
1 parent 4e1673eefc
commit 1addedfdd0
9 files changed
+1921 -205

No files matched your search

File diff suppressed because it is too large. Load diff
+30 -171
View File
@@ -17,151 +17,43 @@
mod harness;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use alksocks::composed::{bindfirst_accept, register_forwarded_serving};
use alksocks::consumer::BindSession;
use alksocks::rfc1928::util::target_addr::ToTargetAddr;
use alktunnels::params::{Substrate, SubstrateAddr};
use alktunnels::producer::{
BindFirstAcceptLoop, BindFn, BoundListener, ResourceRegistry, TargetHandle,
TunnelEstablishError,
};
use alksocks::rfc1928::util::target_addr::{TargetAddr, ToTargetAddr};
use alktunnels::params::Substrate;
use alktunnels::producer::ResourceRegistry;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::sync::Notify;
/// The resource the bind-first producer serves (the tests' only
/// subscription — the registry namespace gate).
const RESOURCE: &str = "bind";
/// The bound address the test bind reports (the "OS-chosen" stand-in —
/// the test's queue listener owns it; fidelity is the assert's job).
const BOUND_PORT: u16 = 42113;
/// The peer address the accepted pairs report (the "app server" — the
/// forwarded op's `peer` param carries it to reply#2).
const PEER_PORT: u16 = 55231;
/// The test bind (the assembly's bind seam): reports the test's
/// bound address + an opaque listener witness.
fn test_bind() -> BindFn {
Arc::new(|_substrate, _backing| {
Box::pin(async {
Ok(BoundListener {
listener: Arc::new(()) as Arc<dyn std::any::Any + Send + Sync>,
bound: SubstrateAddr::HostPort {
host: "127.0.0.1".to_string(),
port: BOUND_PORT,
},
})
})
})
}
/// The accept-queue pair the test accept loop pops from (the alktunnels
/// listener test's shape: a VecDeque + Notify — one accepted pair per
/// dispatcher call).
type AcceptQueue = (
Arc<tokio::sync::Mutex<std::collections::VecDeque<(SubstrateAddr, TargetHandle)>>>,
Arc<tokio::sync::Notify>,
);
fn fresh_queue() -> AcceptQueue {
(
Arc::new(tokio::sync::Mutex::new(std::collections::VecDeque::new())),
Arc::new(Notify::new()),
)
}
/// An accepted "connection" for the test accept loop: a duplex pair
/// with an echo task on the far end (the app server's socket stand-in
/// — the data plane's echo confirms the full round trip).
fn accepted_halves(peer_addr: (&str, u16)) -> (SubstrateAddr, TargetHandle) {
let (consumer_side, target_side) = tokio::io::duplex(64 * 1024);
let (mut t_read, mut t_write) = tokio::io::split(target_side);
tokio::spawn(async move {
let mut buf = vec![0u8; 8192];
loop {
match t_read.read(&mut buf).await {
Ok(0) | Err(_) => return,
Ok(n) => {
if t_write.write_all(&buf[..n]).await.is_err() {
return;
}
}
}
}
});
let (c_read, c_write) = tokio::io::split(consumer_side);
(
SubstrateAddr::HostPort {
host: peer_addr.0.to_string(),
port: peer_addr.1,
},
TargetHandle {
read: Box::new(c_read),
write: Box::new(c_write),
},
)
}
/// The test accept loop (the dispatcher invokes the closure per
/// accept): pop one accepted pair, or park until notified.
fn queue_loop(queue: AcceptQueue) -> BindFirstAcceptLoop {
let (queue, notify) = queue;
Arc::new(move |_listener| {
let queue = Arc::clone(&queue);
let notify = Arc::clone(&notify);
Box::pin(async move {
let popped = {
let notified = std::pin::pin!(notify.notified());
let mut q = queue.lock().await;
if let Some(pair) = q.pop_front() {
Some(pair)
} else {
drop(q);
notified.await;
let mut q = queue.lock().await;
q.pop_front()
}
};
popped.ok_or(TunnelEstablishError::DialFailed(
"listener closed".to_string(),
))
})
})
}
/// Push an accepted pair into the test's accept queue.
async fn push_accept(queue: &AcceptQueue, peer: (&str, u16)) {
let (peer_addr, handle) = accepted_halves(peer);
queue.0.lock().await.push_back((peer_addr, handle));
queue.1.notify_waiters();
}
use harness::{
assert_composed_channels, composed_fresh_queue, composed_opener_identity, composed_push_accept,
composed_queue_loop, composed_scopeless_opener_identity, composed_test_bind,
wire_bindfirst_composed, COMPOSED_BOUND_PORT, COMPOSED_PEER_PORT, COMPOSED_RESOURCE,
};
/// The full wiring: the bind-first topology with the composed accept
/// factory + the forwarded serving registered post-hoc on the opener's
/// registry. Returns everything the tests drive.
struct Suite {
topo: harness::BindFirstTopology,
queue: AcceptQueue,
queue: harness::ComposedAcceptQueue,
}
async fn wire_suite() -> Suite {
wire_suite_with_identity(harness::composed_opener_identity()).await
wire_suite_with_identity(composed_opener_identity()).await
}
async fn wire_suite_with_identity(identity: alkcall::core::auth::Identity) -> Suite {
let resource_registry = ResourceRegistry::new();
resource_registry
.register(RESOURCE, Substrate::Tcp, "assembly-owned")
.register(COMPOSED_RESOURCE, Substrate::Tcp, "assembly-owned")
.await;
let queue = fresh_queue();
let topo = harness::wire_bindfirst_composed(
let queue = composed_fresh_queue();
let topo = wire_bindfirst_composed(
resource_registry,
test_bind(),
queue_loop(queue.clone()),
composed_test_bind(),
composed_queue_loop(queue.clone()),
None, // the SOCKS5 openable registers post-hoc, below — the composed factory needs the opener's client
identity,
)
@@ -180,7 +72,7 @@ async fn wire_suite_with_identity(identity: alkcall::core::auth::Identity) -> Su
// producer's registry — with the REAL composed factory, which
// closes over the opener's client + the correlation registry (both
// only exist now).
let factory = bindfirst_accept(Arc::clone(&topo.opener), Arc::clone(&registry), RESOURCE);
let factory = bindfirst_accept(topo.opener.clone(), registry, COMPOSED_RESOURCE);
let core = alkcall::channels::operations::ChannelCore::new(
topo.producer_manager.clone(),
alkcall::channels::policy::default_policy(),
@@ -212,33 +104,6 @@ async fn bind_session(suite: &Suite) -> BindSession {
.expect("the session binds through the composed accept")
}
/// The no-leak probe: the opener side ends with channel 0 (+ the
/// live channels the test names); the producer side ends with channel
/// 0, the SOCKS5 channels the test names, AND any odd id — the
/// producer-side ADOPTED forwarded channels (opener-allocated ids,
/// adopted inside the producer's `open_forwarded` caller; per-session
/// ownership per alktunnels ADR-005 — their entries reap at transport
/// EOF, not at the listener's teardown; upstream's own bind-first
/// suites make the same concession in `no_stray_channels`).
async fn assert_only_channels(
topo: &harness::BindFirstTopology,
opener_extra: &[u32],
producer_extra: &[u32],
) {
let opener_ids = topo.opener.manager().channel_ids();
let producer_ids = topo.producer_manager.channel_ids();
let opener_ok = opener_ids
.iter()
.all(|id| *id == 0 || opener_extra.contains(id));
let producer_ok = producer_ids
.iter()
.all(|id| *id == 0 || *id % 2 == 1 || producer_extra.contains(id));
assert!(
opener_ok && producer_ok,
"channel sets: opener={opener_ids:?} producer={producer_ids:?}"
);
}
/// THE gate: the full composed BIND story — reply#1 carries the real
/// `bound` (the graduation's vanilla fidelity), an app server "dials"
/// (the queue accept), reply#2 carries the real peer (the forwarded
@@ -268,10 +133,7 @@ async fn composed_bind_full_story_bound_peer_and_round_trip() {
let bind_addr = session.reply1().expect("reply#1").bind_addr;
assert_eq!(
bind_addr,
alksocks::rfc1928::util::target_addr::TargetAddr::Ip(SocketAddr::from((
[127, 0, 0, 1],
BOUND_PORT
))),
TargetAddr::Ip(SocketAddr::from(([127, 0, 0, 1], COMPOSED_BOUND_PORT))),
"reply#1 advertises the real OS-chosen bound"
);
@@ -279,7 +141,7 @@ async fn composed_bind_full_story_bound_peer_and_round_trip() {
// producer's accept loop opens the forwarded channel toward the
// opener; the correlation registry delivers it into the accept
// future; reply#2 carries the real peer.
push_accept(&suite.queue, ("10.0.0.1", PEER_PORT)).await;
composed_push_accept(&suite.queue, ("10.0.0.1", COMPOSED_PEER_PORT)).await;
let mut plane = tokio::time::timeout(Duration::from_secs(10), session.await_inbound())
.await
.expect("reply#2 within timeout")
@@ -287,10 +149,7 @@ async fn composed_bind_full_story_bound_peer_and_round_trip() {
.expect("the plane exists");
assert_eq!(
plane.peer,
alksocks::rfc1928::util::target_addr::TargetAddr::Ip(SocketAddr::from((
[10, 0, 0, 1],
PEER_PORT
))),
TargetAddr::Ip(SocketAddr::from(([10, 0, 0, 1], COMPOSED_PEER_PORT))),
"reply#2 carries the forwarded op's real peer"
);
@@ -324,10 +183,10 @@ async fn composed_bind_full_story_bound_peer_and_round_trip() {
// The opener side holds only channel 0 (the producer-allocated
// forwarded channel's entry rides the opener's manager per its own
// adopter contract — alktunnels ADR-005). The producer side may
// retain the adopted forwarded channel (see `assert_only_channels`
// retain the adopted forwarded channel (see `assert_composed_channels`
// — its adopter's session dropped with the plane; the entry reaps
// at transport EOF, not at the listener's).
assert_only_channels(&suite.topo, &[0], &[channel_id]).await;
assert_composed_channels(&suite.topo, &[0], &[channel_id]).await;
}
/// The client-gone teardown (the drop-witness): dropping the BIND
@@ -382,7 +241,7 @@ async fn client_gone_teardown_releases_the_listener() {
suite.queue.0.lock().await.is_empty(),
"the dropped listener consumed nothing"
);
assert_only_channels(&suite.topo, &[0], &[0]).await;
assert_composed_channels(&suite.topo, &[0], &[0]).await;
}
/// Two concurrent BIND sessions get distinct tokens (the mint is
@@ -401,8 +260,8 @@ async fn concurrent_bind_sessions_get_distinct_tokens() {
// distinct tokens — observably: both sessions resolve their own
// forwarded sessions, the correlation delivered each to the right
// waiter).
push_accept(&suite.queue, ("10.0.0.1", PEER_PORT)).await;
push_accept(&suite.queue, ("10.0.0.2", PEER_PORT + 1)).await;
composed_push_accept(&suite.queue, ("10.0.0.1", COMPOSED_PEER_PORT)).await;
composed_push_accept(&suite.queue, ("10.0.0.2", COMPOSED_PEER_PORT + 1)).await;
let mut p1 = tokio::time::timeout(Duration::from_secs(10), s1.await_inbound())
.await
@@ -442,7 +301,7 @@ async fn concurrent_bind_sessions_get_distinct_tokens() {
assert!(s1.close().await);
assert!(s2.close().await);
tokio::time::sleep(Duration::from_millis(300)).await;
assert_only_channels(&suite.topo, &[0], &[0]).await;
assert_composed_channels(&suite.topo, &[0], &[0]).await;
}
/// A forwarded-open failure is per-accept isolated: an opener identity
@@ -452,13 +311,13 @@ async fn concurrent_bind_sessions_get_distinct_tokens() {
/// usable, and the failure does not tear the listener down.
#[tokio::test]
async fn forwarded_open_failure_is_per_accept_isolated() {
let suite = wire_suite_with_identity(harness::composed_scopeless_opener_identity()).await;
let suite = wire_suite_with_identity(composed_scopeless_opener_identity()).await;
let mut session = bind_session(&suite).await;
let _ = session.reply1().expect("reply#1");
// The accept fires (the queue accept consumed — the producer's
// accept loop is alive), but the forwarded open fails FORBIDDEN.
push_accept(&suite.queue, ("10.0.0.1", PEER_PORT)).await;
composed_push_accept(&suite.queue, ("10.0.0.1", COMPOSED_PEER_PORT)).await;
// The forwarded-open failure is per-accept isolated AT THE TUNNEL
// LAYER: the producer's accept loop drops that accepted connection
@@ -468,7 +327,7 @@ async fn forwarded_open_failure_is_per_accept_isolated() {
// accepts and the listener survives (the alktunnels producer
// suite's `forwarded_open_failure_isolates_the_accept` pin, end to
// end).
push_accept(&suite.queue, ("10.0.0.1", PEER_PORT)).await;
composed_push_accept(&suite.queue, ("10.0.0.1", COMPOSED_PEER_PORT)).await;
tokio::time::sleep(Duration::from_millis(300)).await;
assert!(
suite.queue.0.lock().await.is_empty(),
@@ -493,10 +352,10 @@ async fn late_forwarded_open_after_client_gone_drops_the_channel() {
// arriving in the race window may reach the dispatcher before the
// EOF sentinel does; the forwarded open then finds a dead oneshot
// (the correlation miss) and drops the channel.
push_accept(&suite.queue, ("10.0.0.1", PEER_PORT)).await;
composed_push_accept(&suite.queue, ("10.0.0.1", COMPOSED_PEER_PORT)).await;
tokio::time::sleep(Duration::from_millis(500)).await;
// Either the listener was already gone (the accept queued
// unconsumed) or the forwarded channel dropped at birth — either
// way: no leaked channels on the opener side.
assert_only_channels(&suite.topo, &[0], &[0]).await;
assert_composed_channels(&suite.topo, &[0], &[0]).await;
}
+27
View File
@@ -161,6 +161,33 @@ async fn datagrams_round_trip_per_destination_multiple_tunnels() {
.expect_err("next_reply stays Pending when nothing is ready");
}
/// A fresh association (no tunnel ever opened) parks in `next_reply`
/// — the stage's select is governed by the channel branch until the
/// first datagram opens a tunnel (the assembly suite's drift finding:
/// the ended-relay `Err` fired on the never-opened table too, killing
/// every quiescent association at birth). Pinned on its own.
#[tokio::test]
async fn fresh_association_parks_before_any_tunnel_is_opened() {
let dial_count = Arc::new(AtomicUsize::new(0));
let topo = wire_direct(counting_udp_echo_dial(Arc::clone(&dial_count))).await;
let mut egress = egress_for(&topo, 8).await;
// The never-opened table: `next_reply` parks (Pending), never an
// instantly-ready `Err` (the stage would end the association
// before its first datagram).
{
let mut poll = egress.next_reply();
tokio::time::timeout(std::time::Duration::from_millis(100), &mut poll)
.await
.expect_err("the fresh association's next_reply parks");
}
// After the first datagram opens a tunnel, the normal contract
// holds (the round-trip works).
round_trip(egress.as_mut(), &dest(53), b"after").await;
assert_eq!(dial_count.load(Ordering::SeqCst), 1);
}
#[tokio::test]
async fn lru_eviction_reopens_evicted_destinations() {
let dial_count = Arc::new(AtomicUsize::new(0));
+176
View File
@@ -1211,6 +1211,182 @@ pub fn composed_scopeless_opener_identity() -> Identity {
}
}
/// The composed suite's full identity: the five gates the composed
/// three-command story crosses (`tunnel:open` + `tunnel:direct` +
/// `tunnel:forwarded` + `socks5:open` + `socks5:bind`) — the assembly
/// suite's producer registration serves the direct op (CONNECT +
/// ASSOCIATE's tunnels) beside the bind-first op (BIND's listener).
pub fn composed_full_opener_identity() -> Identity {
Identity {
id: "composed-opener-full".to_string(),
scopes: vec![
alktunnels::TUNNEL_OPEN_SCOPE.to_string(),
alktunnels::TUNNEL_DIRECT_SCOPE.to_string(),
alktunnels::TUNNEL_FORWARDED_SCOPE.to_string(),
SOCKS5_OPEN_SCOPE.to_string(),
SOCKS5_BIND_SCOPE.to_string(),
],
resources: HashMap::new(),
}
}
// --- The composed accept's shared machinery (review 002 N-5) ----------------
//
// The bind/accept-queue stand-ins and the channel-set probe the
// composed suites consume — single-sourced here now that two suites
// (composed-accept + assembly-integration) drive the same shapes.
/// The resource the bind-first producer serves (the composed suites'
/// only subscription — the registry namespace gate).
pub const COMPOSED_RESOURCE: &str = "bind";
/// The bound address the test bind reports (the "OS-chosen" stand-in —
/// the test's queue listener owns it; fidelity is the assert's job).
pub const COMPOSED_BOUND_PORT: u16 = 42113;
/// The peer address the accepted pairs report (the "app server" — the
/// forwarded op's `peer` param carries it to reply#2).
pub const COMPOSED_PEER_PORT: u16 = 55231;
/// The test bind (the assembly's bind seam): reports the test's
/// bound address + an opaque listener witness.
pub fn composed_test_bind() -> alktunnels::producer::BindFn {
Arc::new(|_substrate, _backing| {
Box::pin(async {
Ok(alktunnels::producer::BoundListener {
listener: Arc::new(()) as Arc<dyn std::any::Any + Send + Sync>,
bound: alktunnels::params::SubstrateAddr::HostPort {
host: "127.0.0.1".to_string(),
port: COMPOSED_BOUND_PORT,
},
})
})
})
}
/// The accept-queue pair the test accept loop pops from (the alktunnels
/// listener test's shape: a VecDeque + Notify — one accepted pair per
/// dispatcher call).
pub type ComposedAcceptQueue = (
Arc<
tokio::sync::Mutex<
std::collections::VecDeque<(
alktunnels::params::SubstrateAddr,
alktunnels::producer::TargetHandle,
)>,
>,
>,
Arc<tokio::sync::Notify>,
);
pub fn composed_fresh_queue() -> ComposedAcceptQueue {
(
Arc::new(tokio::sync::Mutex::new(std::collections::VecDeque::new())),
Arc::new(tokio::sync::Notify::new()),
)
}
/// An accepted "connection" for the test accept loop: a duplex pair
/// with an echo task on the far end (the app server's socket stand-in
/// — the data plane's echo confirms the full round trip).
fn composed_accepted_halves(
peer_addr: (&str, u16),
) -> (
alktunnels::params::SubstrateAddr,
alktunnels::producer::TargetHandle,
) {
let (consumer_side, target_side) = tokio::io::duplex(64 * 1024);
let (mut t_read, mut t_write) = tokio::io::split(target_side);
tokio::spawn(async move {
let mut buf = vec![0u8; 8192];
loop {
match t_read.read(&mut buf).await {
Ok(0) | Err(_) => return,
Ok(n) => {
if t_write.write_all(&buf[..n]).await.is_err() {
return;
}
}
}
}
});
let (c_read, c_write) = tokio::io::split(consumer_side);
(
alktunnels::params::SubstrateAddr::HostPort {
host: peer_addr.0.to_string(),
port: peer_addr.1,
},
alktunnels::producer::TargetHandle {
read: Box::new(c_read),
write: Box::new(c_write),
},
)
}
/// The test accept loop (the dispatcher invokes the closure per
/// accept): pop one accepted pair, or park until notified.
pub fn composed_queue_loop(
queue: ComposedAcceptQueue,
) -> alktunnels::producer::BindFirstAcceptLoop {
let (queue, notify) = queue;
Arc::new(move |_listener| {
let queue = Arc::clone(&queue);
let notify = Arc::clone(&notify);
Box::pin(async move {
let popped = {
let notified = std::pin::pin!(notify.notified());
let mut q = queue.lock().await;
if let Some(pair) = q.pop_front() {
Some(pair)
} else {
drop(q);
notified.await;
let mut q = queue.lock().await;
q.pop_front()
}
};
popped.ok_or(alktunnels::TunnelEstablishError::DialFailed(
"listener closed".to_string(),
))
})
})
}
/// Push an accepted pair into the test's accept queue.
pub async fn composed_push_accept(queue: &ComposedAcceptQueue, peer: (&str, u16)) {
let (peer_addr, handle) = composed_accepted_halves(peer);
queue.0.lock().await.push_back((peer_addr, handle));
queue.1.notify_waiters();
}
/// The composed suites' no-leak probe: the opener side ends with
/// channel 0 (+ the live channels the test names); the producer side
/// ends with channel 0, the SOCKS5 channels the test names, AND any
/// odd id — the producer-side ADOPTED forwarded channels
/// (opener-allocated ids, adopted inside the producer's
/// `open_forwarded` caller; per-session ownership per alktunnels
/// ADR-005 — their entries reap at transport EOF, not at the
/// listener's teardown; upstream's own bind-first suites make the
/// same concession in `no_stray_channels`).
pub async fn assert_composed_channels(
topo: &BindFirstTopology,
opener_extra: &[u32],
producer_extra: &[u32],
) {
let opener_ids = topo.opener.manager().channel_ids();
let producer_ids = topo.producer_manager.channel_ids();
let opener_ok = opener_ids
.iter()
.all(|id| *id == 0 || opener_extra.contains(id));
let producer_ok = producer_ids
.iter()
.all(|id| *id == 0 || *id % 2 == 1 || producer_extra.contains(id));
assert!(
opener_ok && producer_ok,
"channel sets: opener={opener_ids:?} producer={producer_ids:?}"
);
}
#[cfg(test)]
mod tests {
use super::*;