Files
alksocks/tasks/implementation/fix-producer-gate-policy.md
T
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

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, which is correct); the substrate-free entry derives it from seam presence. For the batch-2 local backend, which is 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, so 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, but 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, since 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.