- C-3: Socks5ProducerBuilder.bind_policy(bool) — the substrate-free entry's explicit BIND gate, defaulting to the accept seam's presence; seam presence is never the sole gate - D-2: RegisterMode::ProducerBuilder + an integration test wiring the full builder through .register(...) serving all three commands - S-1: run_datagram_stage's doc names the hot-Ok(None) residual - N-2: wire.md §UDP ASSOCIATE carries the inline-send backpressure line Verified: cargo test (352 green), clippy/fmt/doc clean, wasm checks clean.
7.9 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level |
|---|---|---|---|---|---|---|---|
| implementation/fix-producer-gate-policy | Fix — producer BIND gate policy + register coverage + stage doc lines (review-002 C-3, D-2, S-1, N-2) | completed | narrow | medium | component | implementation |
Description
Close review 002's producer-side API-shape hazard and the
unexercised assembly surface, plus the two doc-line residues that
ride the same files
(docs/reviews/002-protocol-core-implementation-review.md):
-
C-3 (minor) —
serve_socks5_overderivescan_bindfromaccept.is_some()(src/producer.rs:630-648): factory-wired ⇒ BIND allowed, with no way to say "factory wired but gate closed" on the substrate-free entry. The pump handler (the channels path) resolves the gate from the per-call opener identity (ADR-006 — correct); the substrate-free entry derives it from seam presence. For the batch-2localbackend — the documented consumer of this entry — the local door's BIND authorization has no seam to plug into except factory presence, which is exactly the shape that invites "listener configured" ⇒ "listener permitted" (the conflation ADR-006 exists to prevent).Fix per the review's sketch: widen the substrate-free entry's gate to an explicit policy input —
serve_socks5_over_with_gateor a builder field.bind_policy(bool)defaulting toaccept.is_some()for backward-compat with the landed tests. The pump handler's identity-derived gate is unaffected. One test: a producer with an accept factory + explicitcan_bind=falserefuses 0x02. -
D-2 (minor) —
Socks5Producer::registerand the builder'segress/acceptsetters are unexercised public surface (0% covered; the harness registers via the free functionregister_socks5_openableinstead, and the builder test drives onlyserve_socks5_over). The spec'd assembly shape (producer.md§Wiring) is the one assembly path with no test — a signature drift or delegation bug would pass the suite.Fix: one integration test wiring a full
Socks5Producer::builder().dial(...).egress(...).accept(...)through.register(...)and serving all three commands over the harness topology. This is the natural home for C-3's gate test. -
S-1 (minor) — the datagram stage's residual hot-
Ok(None)posture, named as a decision. The R-1 yield-guard makes the stage livelock-safe; the residual is a misbehaving egress returningOk(None)at full speed = a yielding busy-wait (bounded by the egress's own correctness, unbounded by rate). Per the review: a posture note, not a backoff/budget (which would penalize the compliant path). One doc line onrun_datagram_stagenaming the residual. -
N-2 (observation) — one line in wire.md §UDP ASSOCIATE: the per-datagram
egress.sendis awaited inline (the stage interleaves sends with reply drains); a slow egress applies backpressure to the association, not to other channels. The batch-2 composed-egress author inherits the bound knowingly.
Acceptance Criteria
- The substrate-free entry's BIND gate is an explicit policy
input (with-gate entry or builder field);
accept.is_some()remains only as the backward-compat default, never the sole gate - Test: accept factory wired + explicit
can_bind=false⇒ BIND refused 0x02; CONNECT/ASSOCIATE still serve - The pump handler's identity-derived gate is unchanged (the existing identity matrix stays green)
- Integration test: full builder (
dial+egress+accept) through.register(...)serving all three commands over the harness topology run_datagram_stage's doc names the hot-Ok(None)residual- wire.md §UDP ASSOCIATE carries the inline-send backpressure line
cargo testgreen; clippy/fmt clean; both wasm checks clean
References
- docs/reviews/002-protocol-core-implementation-review.md §C-3, §D-2, §S-1, §N-2
- docs/architecture/decisions/006-access-control.md (the gate posture — "listener configured" ≠ "listener permitted")
- docs/architecture/producer.md §Wiring (the spec'd assembly shape)
- docs/architecture/wire.md §UDP ASSOCIATE (the N-2 line's home)
- tasks/implementation/core-producer-bind.md (the landed gate)
Notes
- C-3 shape chosen: the builder field (
.bind_policy(bool)), not a separateserve_socks5_over_with_gateentry — the review's sketch allowed either; a builder field keeps the entry's signature untouched (oneserve_socks5_overshape), rides the existing builder chain, and the policy is resolved once atbuild()(self.bind_policy.unwrap_or(self.accept.is_some())—Option<bool>on the builder,boolon the built producer). The free functionserve_socks5_over(already gate-explicit) and the pump handler (identity-derived, ADR-006) are untouched. - D-2 vehicle: a new
RegisterMode::ProducerBuilderintests/harness.rswiring the full builder chain (.dial().egress().accept()+ the optional.bind_policy()) throughSocks5Producer::register— the harness's registration block gains a third arm (sameChannelCore+register_openable_with_establishershape as the others, via the delegated method). The integration test (producer_builder_registers_and_serves_all_three_commandsintests/core-integration.rs) serves CONNECT + ASSOCIATE + BIND over the topology and asserts the identity witness (proving the builder-delegated establisher ran). C-3's gate tests live in the producer unit suite (builder_bind_policy_false_refuses_bind_ while_connect_associate_serve,builder_default_bind_policy_ follows_accept_presence) — the substrate-free entry is a unit shape; the builder-through-register surface is the integration one. - The
bind_policy(false)test pins: 0x02 on BIND with the accept factory wired, the factory never called (no phantom listener), the in-streamAuthenticationRejectedsession error, and CONNECT + ASSOCIATE still serving the same producer. The default test pins the backward-compat reading: accept wired + policy unwired ⇒ BIND serves (factory ran); accept unwired + policy unwired ⇒ 0x02. - S-1/N-2 are doc lines only (per the review's recommendation —
posture notes, not backoff/budget code): a residual paragraph on
run_datagram_stage(hot-Ok(None)= a yielding busy-wait, bounded by the egress's own correctness, accepted-uncovered by design) and a bolded inline-send sentence on the wire.md §UDP ASSOCIATE producer-egress bullet.
Summary
All four items landed (C-3, D-2, S-1, N-2). Socks5ProducerBuilder
gained .bind_policy(bool) — the substrate-free entry's explicit
BIND gate, defaulting to the accept seam's presence (the built
producer resolves it to a plain bool); Socks5Producer:: serve_socks5_over now consults the policy instead of
accept.is_some(). The pump handler's identity-derived gate is
byte-identical (the identity matrix stayed green untouched). Two
unit tests pin the policy (explicit-closed refuses 0x02 with the
factory un-called while CONNECT/ASSOCIATE serve; the
presence-default both ways). A new harness mode
(RegisterMode::ProducerBuilder) plus one integration test wires
the full builder through .register(...) and serves all three
commands end to end — the spec'd assembly shape is no longer
unexercised. Doc lines: run_datagram_stage names the hot-Ok(None)
residual (S-1); wire.md §UDP ASSOCIATE carries the inline-send
backpressure bound (N-2). Verification: cargo test 352 green (125
lib + 38 associate + 41 bind + 40 connect + 41 integration + 30
harness + 37 differential + 3 doctests, 2 ignored — the two new
unit tests and the one new integration test added to the review's
348 baseline), cargo clippy --all-targets -- -D warnings clean,
cargo fmt --check clean, cargo doc --no-deps clean, both wasm
checks (cargo check + clippy -D warnings, wasm32) clean.