Files
glm-5.3-flash e196ed43e3 docs: remove em dashes from repo prose and forbid them going forward
- Rewrote em dash prose across the whole tree outside docs/reviews/
  (the historical reviews stay verbatim, matching the alktunnels and
  alktty precedent): src doc comments and inline comments (587),
  docs/architecture (319), docs/research + sdd_process (484), tests
  (361), tasks (899), fuzz (28), README/CHANGELOG/Cargo.toml (77),
  AGENTS.md (85), and the .opencode/agents prompts (109)
- Fenced content follows the precedent: comments, ASCII-diagram label
  separators, and template placeholders rewritten; wire data, JSON/BAST
  documents, byte diagrams, and transcripts survive verbatim; assert,
  expect, panic, error-display, and format message literals rewritten
  as prose; no code, fixture, or test data change
- src/rfc1928/LICENSE: the two dashes in alksocks' own provenance
  header rewritten; the MIT legal text untouched
- AGENTS.md adds Project Convention 18: no em dashes in prose; varied
  restructure, no spaced-hyphen substitutes, fenced prose rule, en
  dash numeric ranges exempt, docs/reviews/ exempt from the greps
- tasks/*.md frontmatter: the name values rewritten by the sweep are
  now quoted (a raw colon inside an unquoted YAML scalar parsed as
  null); taskgraph validate reports 37 tasks, no issues

Verification: cargo test; cargo clippy --all-targets -D warnings;
cargo fmt --check; cargo doc --no-deps; fuzz corpus replay
(cargo test --manifest-path fuzz/shared/Cargo.toml); wasm check and
clippy for wasm32-unknown-unknown with -D warnings; em dash grep over
the repo returns matches only in docs/reviews/ and the bast.md BAST
JSON data fence
2026-11-03 19:31:53 +00:00

191 lines
10 KiB
Markdown

---
id: implementation/composed-accept
name: "Composed: the bind-first accept (the listener on a listen resource)"
status: completed
depends_on: [implementation/composed-egress]
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
The `composed` feature's accept provider (ADR-008's composed
variants; ADR-007 variant 2): the BIND accept whose listener lives on
an alktunnels 0.2.0 bind-first listen resource
(`register_tunnel_bindfirst_openable` + `BindFirstInjections`),
per-listener ACL for free, hub-relayable. **Vanilla-fidelity with no
stand-ins since the graduation** (ADR-007): reply#1 advertises the
real OS-chosen `bound` (the open-op reply's `bound` field, alkcall
0.8.0 reply projection); reply#2 carries the real peer (the forwarded
op's `peer` param).
Deliverables:
1. **`src/composed/` additions** (feature `composed`): the composed
accept, named per producer.md §Wiring:
- `bindfirst_accept(...)` → `BindAcceptFactory`: the POC #5
`composed_bind_accept` shape **updated to the graduation**:
the POC's pinned workaround (reply#1 first, the tunnel open
rides the accept future; the factory carries an expected
`bind_addr`) is **retired** (ADR-007 §Consequences). The landed
shape: the factory opens the bind-first listen channel
(`TunnelListener::open`; the reply projection surfaces
`bound`), reply#1 advertises the real `bound`, and the accept
future correlates the forwarded sessions (the `listen` token →
the adopted forwarded sessions the assembly correlates; the
forwarded op's `peer` param is reply#2's peer).
- **The correlation shape is the design center** (the POC's
upstream-ask ledger retired by the graduation): the factory
returns `BindAccept` whose accept future waits for the ONE
forwarded session matching the listener's token. The
correlation registry is assembly-side bookkeeping (the
alktunnels `TunnelListener` docs: "forwarded sessions arrive as
ordinary adopted sessions on the manager; the listener does not
own the receipt queue"). The provider owns: the
`register_tunnel_forwarded_openable` serving side (the opener
half: the SOCKS5 producer's connection must serve the
forwarded op), the token→waiter map, and the per-forwarded
pump. Design the wait shape against the landed alkcall 0.8.0
adopt surface (`manager.adopt_channel`) and record it in Notes.
- **The Sync-bound wrinkle** (shared with composed-egress): the
accepted halves must be `Box<dyn AsyncRead + Send + Sync +
Unpin>` (`BindTargetHandle`'s payloads); alktunnels' `Taken
Halves` yields `Send + Unpin` only. The owner-wrapper rides
**inside the handle payloads** (egress.rs's documented posture):
the wrapper owns the adopted channel session so dropping the
handle drops everything (the teardown-at-birth discipline,
compiler-enforced). This is the load-bearing wrapper: design it
once (with composed-egress's needs), pin it with a test.
- **Peer-address fidelity**: the forwarded op's `peer` param is
reply#2's real peer (the graduation's win); the wrapper-aware
sentinel (`0.0.0.0:0`) is the in-band default's shape, not this
variant's.
- **The establishment bound covers only the open-op round trip**
(ADR-007): the accept wait lives in the teardown-governed pump
phase: the quiet wait's `select!` drop of the accept future IS
the client-gone teardown; the listener (inside the future)
closes with it. The forwarded-op correlation must survive a
client-gone drop without leaking the listener (the accept
future owns it end-to-end, which is the seam contract).
- Module doc: names the ADRs (007/008), the graduation's
vanilla-fidelity win, the correlation shape, the owner-wrapper.
2. **Tests** (`tests/composed-accept.rs`): the full composed BIND
story over channels: a bind-first tunnel producer
(`register_tunnel_bindfirst_openable` + injections), the SOCKS5
producer with `composed::bindfirst_accept(...)`, a consumer BIND
session: reply#1 carries the real `bound`, an app server dials
the far listener, reply#2 carries the real peer, the pass-through
data plane round-trips; the client-gone teardown releases the
listener (drop-witness); two concurrent BIND sessions get
distinct tokens; a forwarded-open failure is per-accept isolated.
## Acceptance Criteria
- [ ] `cargo test` passes; `cargo test --all-features` passes
- [ ] `cargo check --features composed --target wasm32-unknown-
unknown` clean (hard gate)
- [ ] `cargo clippy --all-targets --all-features -- -D warnings`
clean; `cargo fmt --check` clean
- [ ] The graduation's shape lands (reply#1 = the real `bound`;
reply#2 = the forwarded op's real `peer`, no expected-addr
stand-in), pinned by test
- [ ] The owner-wrapper rides inside the handle payloads (Sync
halves; teardown structural), pinned by test
- [ ] The client-gone teardown releases the listener (the accept
future owns it end-to-end), pinned by drop-witness test
- [ ] The forwarded-op serving side + correlation land; per-accept
isolation, pinned by tests
## References
- docs/architecture/decisions/007-bind-in-base.md (variant 2 + the
graduation), 008-provider-defaults.md, 004 (the accept seam's
contracts), 006 (the command gate)
- docs/architecture/producer.md §Wiring
(`composed::bindfirst_accept`), §The Three Seams
- POC reference: `/workspace/alksocks-bind-poc/src/accept.rs`
(`composed_bind_accept`, the superseded-in-shape workaround, the
owner-wrapper's first witness)
- Landed surfaces: `src/egress.rs` (`BindAccept`/`BindTargetHandle`,
the contracts), alktunnels 0.2.0 (`register_tunnel_bindfirst_
openable`, `BindFirstInjections`, `TunnelListener`,
`register_tunnel_forwarded_openable`, `ForwardedHandler`)
## Notes
- **The correlation shape** (the design center): a
`CorrelationRegistry` (`Arc<tokio::sync::Mutex<HashMap<u32,
oneshot::Sender<ForwardedSession>>>>`) keyed on the `listen` token.
The factory inserts the token → oneshot right after
`TunnelListener::open`; the provider's shared forwarded handler
(`make_forwarded_handler`, registered via
`register_forwarded_serving` on the SOCKS5 producer's connection)
removes the entry on the arriving forwarded open and delivers
`ForwardedSession { far duplex halves, peer }`. A dropped receiver
(client-gone) or a miss makes the handler drop the forwarded
channel stream: teardown-at-birth, R-02 (the inline `pump_bidi`
await in `register_openable`'s wrapper then reaps the entry).
- **The Sync-bound wrinkle**: alktunnels' halves are `Send + Unpin`
only; the landed `BindTargetHandle` payloads carry `+ Sync`. Two
adapters pin the no-unsafe Sync proof (`Mutex<T: Send>` is `Sync`;
contention never occurs, since only one pump task runs at a time):
`SyncHalves`
(read half) and `OwnedWrite` (write half + the owner inside).
- **The owner-wrapper rides inside the payloads**
(`ForwardedOwner`): the write payload owns the `TunnelListener`;
dropping the handle drops the listener → the listen channel EOFs →
the far teardown cascades (teardown structural,
compiler-enforced). The edition-2021 disjoint-capture trap (an
earlier draft captured `listener.channel_id`, dropping the
listener at the factory's return, that is EOF at birth) is why the whole
struct is rebound into the accept future.
- **The three-party bridge pump**: the forwarded handler bridges the
forwarded `BiStream` to a local duplex pair whose far end travels
into the accept future's payloads. The bridge completes on EITHER
direction's EOF (a `pump_bidi`-then-drop, not a per-direction
shutdown cascade; a three-party half-close cycle would deadlock:
each pump's completion-shutdown needs the next hop's EOF, and the
chain closes on itself). Dropping everything EOFs the SOCKS5
producer-side pump, ends the per-accept pump, and tears the
forwarded channel down.
- **Test concession (producer-side adopted forwarded entry)**: the
producer's `open_forwarded` caller adopts the opener-allocated
forwarded channel id on the producer-side manager; that entry's
reap rides the per-adopter contract + transport EOF, NOT the
listener's teardown (alktunnels ADR-005; upstream's own bind-first
suites make the same concession: `producer_bindfirst.rs`
`no_stray_channels` documents the listen channel, and the
forwarded entries are adopted sessions owned by their adopter).
`assert_only_channels` therefore treats odd producer-side ids as
the adopted-forwarded shape.
- **Forwarded-open failure semantics**: a FORBIDDEN forwarded open is
per-accept isolated AT THE TUNNEL LAYER (the producer's accept loop
drops that connection and keeps serving; the alktunnels
producer-suite pin). The SOCKS5 accept future stays parked; the
SOCKS5 producer never learns of the tunnel-layer failure, so no
reply#2 arrives; the quiet wait is unaffected. Pinned by test.
## Summary
Landed `composed::bindfirst_accept` + `composed::register_forwarded_serving`
(the composed accept, ADR-007 variant 2 / ADR-008) on the alktunnels 0.2.0
bind-first listen resource, at the graduation's vanilla fidelity: reply#1
advertises the open-op reply's real `bound` (the alkcall 0.8.0 reply
projection; no expected-addr stand-in), reply#2 carries the forwarded op's
real `peer` param. The correlation registry (token → oneshot) is assembly
bookkeeping; the provider owns the forwarded-op serving half, the token→waiter
map, and the per-forwarded middle pump (either-EOF completion shape). The
Sync-bound wrinkle is met by `SyncHalves`/`OwnedWrite` (mutex Sync-proof, no
unsafe) with the owner (`ForwardedOwner`, the `TunnelListener`) riding inside
the write payload: dropping the handle drops everything (teardown structural,
compiler-enforced). `tests/composed-accept.rs` pins: the full story (real
`bound`, real `peer`, raw pass-through round trip), the client-gone teardown
(drop-witness: the accept future owns the listener end-to-end; no leaked
listen channel), two concurrent BIND sessions' distinct tokens, per-accept
isolation of a FORBIDDEN forwarded open, and the late-forwarded correlation
miss dropping the channel at birth. Verified: `cargo test`, `cargo test
--all-features`, `cargo check/clippy --target wasm32-unknown-unknown` (hard
gate), `cargo clippy --all-targets --all-features -- -D warnings`,
`cargo fmt --check`: all clean.