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:
1 parent
4e1673eefc
commit
1addedfdd0
9 files changed
+1921
-205
No files matched your search
+15
-2
@@ -194,6 +194,14 @@ pub struct ComposedEgress {
|
||||
/// The factory `Sender` alone does not close the queue (the
|
||||
/// egress holds it) — the count is the design.
|
||||
live_relays: usize,
|
||||
/// Whether any tunnel has ever been opened. The lazy table starts
|
||||
/// empty: a fresh association's `next_reply` parks until the first
|
||||
/// `send` opens a tunnel (the producer's stage selects the channel
|
||||
/// branch first on a quiescent association — the association
|
||||
/// serves until a datagram arrives or the channel EOFs). The
|
||||
/// ended-relay `Err` fires only once at least one tunnel existed
|
||||
/// and every relay has ended (the closed-queue condition).
|
||||
ever_opened: bool,
|
||||
}
|
||||
|
||||
/// The shared inbound queue's message: a tunnel's datagram (keyed by
|
||||
@@ -281,6 +289,7 @@ impl ComposedEgress {
|
||||
inbound: rx,
|
||||
inbound_tx: tx,
|
||||
live_relays: 0,
|
||||
ever_opened: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,6 +315,7 @@ impl ComposedEgress {
|
||||
// bug). The relay task owns the bare read half.
|
||||
tokio::spawn(relay_read(taken.read, self.inbound_tx.clone(), key.clone()));
|
||||
self.live_relays += 1;
|
||||
self.ever_opened = true;
|
||||
self.entries.insert(
|
||||
key.clone(),
|
||||
TableEntry {
|
||||
@@ -368,11 +378,14 @@ impl Egress for ComposedEgress {
|
||||
Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => break,
|
||||
}
|
||||
}
|
||||
if self.live_relays == 0 {
|
||||
if self.ever_opened && self.live_relays == 0 {
|
||||
// Every relay ended (the tunnels ended): a closed
|
||||
// relay set must NOT spin the stage's select (an
|
||||
// instantly-ready None would starve the channel
|
||||
// branch) — end the association instead.
|
||||
// branch) — end the association instead. A fresh
|
||||
// association (no tunnel ever opened) parks here —
|
||||
// the channel branch governs until the first
|
||||
// datagram arrives.
|
||||
return Err(EgressError::General("all tunnel relays ended".into()));
|
||||
}
|
||||
match self.inbound.recv().await {
|
||||
|
||||
+20
-11
@@ -40,21 +40,30 @@
|
||||
//! sessions — [`Socks5ChannelSession`] (CONNECT),
|
||||
//! [`AssociateSession`] (the framed datagram stage),
|
||||
//! [`BindSession`] (the reply#1/reply#2 reader) — each owning its
|
||||
//! channel's teardown; no `Clone`.
|
||||
//! channel's teardown; no `Clone`. The cred-bearing front door's
|
||||
//! re-origination rides the token-carrying surface (`TokenClient`
|
||||
//! + the `*_with_token` constructor twins).
|
||||
//!
|
||||
//! `local` (the socket-backed assembly-layer providers —
|
||||
//! [`crate::local::tcp_dial`] / [`crate::local::udp_egress`] /
|
||||
//! [`crate::local::bind_accept`], ADR-008/009) is feature-gated: the
|
||||
//! default crate is protocol-only and wasm-clean by construction, and
|
||||
//! the composed providers ride `composed`
|
||||
//! ([`crate::composed::direct_egress`] — the alktunnels direct-op
|
||||
//! egress, ADR-008/009).
|
||||
//! The assembly layer (feature-gated, ADR-008/009): `local` (the
|
||||
//! socket-backed providers — `local::tcp_dial` / `local::udp_egress`
|
||||
//! / `local::bind_accept` — plus the producer-side
|
||||
//! `local::ProducerDoor`) and `door` (the consumer's front doors —
|
||||
//! the NoAuth raw-pipe + RFC 1929 cred-bearing `FrontDoor`, the
|
||||
//! `UdpFrontDoor` with the RFC codec at the door boundary only,
|
||||
//! ADR-005/003/014) ride `local`; the composed providers
|
||||
//! (`composed::direct_egress` — the alktunnels direct-op egress,
|
||||
//! `composed::bindfirst_accept` — the bind-first accept,
|
||||
//! ADR-007/008) ride `composed`. The default crate is protocol-only
|
||||
//! and wasm-clean by construction.
|
||||
//!
|
||||
//! Architecture: `docs/architecture/overview.md` (spec set:
|
||||
//! wire.md, producer.md, consumer.md, rfc1928.md); the cross-cutting
|
||||
//! behavior is pinned end to end by `tests/core-integration.rs`
|
||||
//! (one registration, all three commands; concurrency; the teardown
|
||||
//! matrix; the identity/scope matrix; the readiness contract).
|
||||
//! behavior is pinned end to end by `tests/core-integration.rs` (the
|
||||
//! protocol core: one registration, all three commands; concurrency;
|
||||
//! the teardown matrix; the identity/scope matrix; the readiness
|
||||
//! contract) and `tests/assembly-integration.rs` (the assembly
|
||||
//! layer: the full `-D` story through the doors, the composed
|
||||
//! variant set, mixed assembly, the assembly teardown matrix).
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
|
||||
Reference in new issue
Block a user