--- id: implementation/assembly-integration-suite name: "Assembly: the cross-cutting integration suite + docs sync" status: completed depends_on: [implementation/door-udp, implementation/local-backend-producer, implementation/composed-accept] scope: moderate risk: medium impact: phase level: implementation --- ## Description The batch-2 closing task (the core-integration-suite's assembly-layer analogue): the cross-cutting suite that pins the whole assembly story, plus the docs sync that records what landed. The per-component tasks own their verticals; this task owns the compositions and the spec-vs-implementation reconciliation. Deliverables: 1. **Cross-cutting integration tests** (`tests/assembly-integration.rs`): - **The full `-D` story end to end**: a vanilla tun2proxy-shaped client → the cred-bearing TCP door → the channels-native session → the producer with `local` providers → real echo targets: CONNECT + UDP (the RFC-header'd datagram through the UDP door) + BIND (the vanilla BIND conversation through the door) all through one door process against one producer registration (the one-registration-three-commands regression, now through the doors). - **The composed story end to end**: the producer with `composed::direct_egress` + `composed::bindfirst_accept`: CONNECT through the direct op, ASSOCIATE through the lazy table, BIND through the bind-first listener (the ADR-008 composed variant set, all three commands). - **Mixed assembly**: one producer registration serving channels-native sessions AND door clients AND composed seams concurrently (the producer/consumer-roles-not-sides posture; both shapes on one connection). - **Teardown matrix, assembly edition**: door-client disconnect tears the association/channel down (no leaks; the channel count witnesses); the idle bounds fire; fresh sessions succeed immediately after each teardown (no connection poisoning). - **The wasm gates re-verified**: default + `--features composed` wasm checks green (the `local` feature is non-wasm by design; verify it *fails* to build on wasm, the alktty posture, and record the observed error in Notes). 2. **Docs sync** (the architecture-sync check): - `docs/architecture/README.md` Current State + the doc statuses reflect the landed assembly layer (the "no `local`/`composed` code exists yet" lines updated). - `lib.rs` crate docs updated (the "not yet present" paragraph; the doors/providers are landed now). - Any spec drift the implementation surfaced reconciled in the same change, never silently diverged (the batch-1 discipline). 3. **Full verification sweep** (the batch gate's input): `cargo test`, `cargo test --all-features`, clippy `--all-targets --all-features -- -D warnings`, `cargo fmt --check`, both wasm checks (default + composed), and `cargo doc --no-deps` (docs changed). ## Acceptance Criteria - [x] The full `-D` story (vanilla client → door → session → producer → real target) pinned end to end for all three commands - [x] The composed variant set pinned end to end (all three commands) - [x] Mixed assembly (doors + channels-native + composed) concurrent on one producer (pinned) - [x] The assembly teardown matrix pinned (door disconnects, idle bounds, fresh-session-after-teardown) - [x] All four verification targets green (default, all-features, wasm default, wasm composed) - [x] Docs synced (README Current State, lib.rs crate docs, any drift reconciled) ## References - docs/architecture/overview.md (the feature gates + module map; the landed-shape check), consumer.md §The Front Doors, producer.md §Producer-Side Local Backend, §Wiring - docs/architecture/decisions/005/007/008/009/014 - The batch-1 precedent: `tests/core-integration.rs` (the suite shape), `tasks/implementation/core-integration-suite.md` (the task shape) ## Notes > Filled by the implementation agent (2026-09-25). - **The spec drift the implementation surfaced (one real drift, reconciled in `src/composed/mod.rs`):** `ComposedEgress::next_reply` returned the association-end `Err` ("all tunnel relays ended") on a *fresh* association too: the lazy table starts with `live_relays == 0`, so the producer's datagram stage (which parks on `next_reply` when no datagram has arrived) ended every quiescent association at birth. The composed-egress suite drove the egress send-first and never hit it; the assembly suite's `AssociateSession` (open → the stage parks → the first datagram) did. Fix: an `ever_opened` flag; the ended-relay `Err` fires only once at least one tunnel existed and every relay has ended; a never-opened table parks (the readiness contract's `Pending`, the channel branch governs). The landed spec wording (producer.md: "`Pending` when nothing is ready") already stated the correct contract; the implementation was the divergent side. Regression-pinned in `tests/composed-egress.rs` (`fresh_association_parks_before_any_tunnel_is_opened`). - **Two assembly-shape findings recorded in the tests (not code changes; the landed contracts are correct):** - The cred-bearing door's `TokenClient::wrap` takes the client's channel-0 `CallConnection` once (the door-only contract, door-tcp's Notes); the UDP door's association (and any other consumption shape) rides its own channels connection to the same producer registration. The suite pins the assembly story as "one registration, many connections" (consumer.md reconciled). - The RFC conversation is **one command per connection** at every door shape (the cred-bearing TCP door's RFC half consumes the greeting + one command; the producer-side `ProducerDoor`'s `serve_socks5_over` likewise serves one command per accepted socket; a second command's bytes ride the first session's data plane). The vanilla one-command-per-connection shape is pinned by the suite's structure (each command opens its own door connection). - **Harness consolidation (review 002 N-5's discipline):** the composed-accept machinery the assembly suite also consumes (the test bind, the accept queue + loop, the channel-set probe, the identities) moved from `tests/composed-accept.rs` into `tests/harness.rs` (single-sourced; `composed-accept.rs` consumes the same shapes via the harness re-exports (its test bodies are unchanged in behavior). The five-gate composed identity (`composed_full_opener_identity`: `tunnel:open` + `tunnel:direct` + `tunnel:forwarded` + `socks5:open` + `socks5:bind`) is new. - **The wasm gate, observed**: `cargo check --features local --target wasm32-unknown-unknown` fails as designed (the alktty posture); the first error is `error: This wasm target is unsupported by mio. If using Tokio, disable the net feature.` (mio v1.2.3, via `tokio/net` which `local` enables), followed by the mio `sys` module's unresolved imports (`crate::sys::IoSourceState`, `crate::sys::tcp`, `Selector`, `event`). Both default and `--features composed` wasm checks are green. - **The `-D` story's door wiring**: the cred-bearing TCP door + the UDP door each take their own `ChannelClient` (two connections to the one registration); the tests' `DoorTokenProvider` resolves the mapped `door-`-prefixed identity ids (the same producer-side resolution policy shape the door-tcp suite pins). The producer-door test drives the producer's own front door (`ProducerDoor` → `serve_socks5_over` over the `local` provider set) through all three commands. ## Summary > Filled on completion (2026-09-25). Landed the batch-2 closing task: `tests/assembly-integration.rs`, seven cross-cutting integration tests pinning the whole assembly layer end to end (feature `local` + `composed`): 1. **The full `-D` story** (`full_dash_d_story_...`): a vanilla tun2proxy-shaped client through the cred-bearing RFC 1929 TCP door + the UDP door against one producer registration with the `local` providers and real echo targets: CONNECT + BIND (the app server dials the advertised address; reply#2 carries the real peer) + ASSOCIATE (the RFC-header'd datagram through the relay socket) live simultaneously, each exercised mid-life of the others, the door-client disconnect ending only its own sessions, fresh sessions succeeding after (no connection poisoning). 2. **The composed story** (`composed_story_...`): the producer with `composed::direct_egress` + `composed::bindfirst_accept` + a real TCP dial: CONNECT, ASSOCIATE (the lazy table; the direct-op dial count witnesses the lazy open), BIND (the real `bound` + the real `peer` + the pass-through plane); all three commands, all live. 3. **Mixed assembly, twice**: channels-native sessions + door clients concurrent on one registration (the doors on their own connections), and composed seams beside channels-native sessions (the composed suite's producer; teardowns end only their own sessions; the channel-set probe after the settle). 4. **The teardown matrix, assembly edition**: the UDP door's idle bound fires on the vanished door client, the association's channel reaps (the channel count witnesses), a fresh association succeeds immediately (no connection poisoning). 5. **The producer's own front door** (`ProducerDoor`) drives all three commands through one door + one registration (the `-D` story's other half; the faithful-RFC BIND variant). 6. **The `-D` domain-target pin**: a domain-form CONNECT through the NoAuth raw-pipe door resolves at the producer's seam (ADR-012). The reconciled drift: `ComposedEgress`'s never-opened-table association end (fixed in `src/composed/mod.rs`, the `ever_opened` guard; regression-pinned in `tests/composed-egress.rs`). Docs synced: `README.md` Current State (the assembly layer landed; both suites named), `lib.rs` crate docs (the assembly-layer paragraph + both suites named), `consumer.md` §Door variants (the `TokenClient` door-only contract + one-command-per-connection posture reconciled, the drift the suite surfaced, recorded in Notes). Harness: the composed-accept machinery single-sourced into `tests/harness.rs` (the N-5 discipline). Verification: `cargo test` (375 green across 12 targets), `cargo test --all-features` (615 green), `cargo clippy --all-targets -- -D warnings` + `cargo clippy --all-targets --all-features -- -D warnings` clean, `cargo fmt --check` clean, `cargo doc --no-deps` clean (0 warnings), both wasm checks green (default + `--features composed`, check + clippy), and the `local`-on-wasm build failure observed + recorded (the alktty posture: mio's unsupported-wasm error).