Files
alksocks/tasks/implementation/fix-producer-gate-policy.md
T
glm-5.3-flash ff81f2d27b producer: the explicit bind-policy gate + the builder-register pin (review-002 C-3, D-2, S-1, N-2)
- 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.
2026-09-24 18:04:55 +00:00

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):

  1. C-3 (minor) — serve_socks5_over derives can_bind from accept.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-2 local backend — 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_gate or a builder field .bind_policy(bool) defaulting to accept.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 + explicit can_bind=false refuses 0x02.

  2. D-2 (minor) — Socks5Producer::register and the builder's egress/accept setters are unexercised public surface (0% covered; the harness registers via the free function register_socks5_openable instead, and the builder test drives only serve_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.

  3. 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 returning Ok(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 on run_datagram_stage naming the residual.

  4. N-2 (observation) — one line in wire.md §UDP ASSOCIATE: the per-datagram egress.send is 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 test green; 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 separate serve_socks5_over_with_gate entry — the review's sketch allowed either; a builder field keeps the entry's signature untouched (one serve_socks5_over shape), rides the existing builder chain, and the policy is resolved once at build() (self.bind_policy.unwrap_or(self.accept.is_some()) — Option<bool> on the builder, bool on the built producer). The free function serve_socks5_over (already gate-explicit) and the pump handler (identity-derived, ADR-006) are untouched.
  • D-2 vehicle: a new RegisterMode::ProducerBuilder in tests/harness.rs wiring the full builder chain (.dial().egress().accept() + the optional .bind_policy()) through Socks5Producer::register — the harness's registration block gains a third arm (same ChannelCore + register_openable_with_establisher shape as the others, via the delegated method). The integration test (producer_builder_registers_and_serves_all_three_commands in tests/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-stream AuthenticationRejected session 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.