Files
alkssh/tasks/implementation/w4-router-collision.md
T
glm-5.3-flash f4b5f896b4 docs: sweep em dashes from all markdown prose
Renames the written-usage rule into practice across the contract surface:
docs/ (architecture, research, plans, sdd), tasks/ (all 43 task files,
taskgraph validate passes), AGENTS.md, README.md, CHANGELOG.md, and the
.opencode/agents/ prompts. Every replacement is a sense-preserving
rewrite (colons, semicolons, commas, parentheses, sentence splits, varied),
never a mechanical swap; no spaced hyphens were substituted. Enum fields,
frontmatter, tables, wire data, and cited identifiers survive verbatim;
en dashes inside numeric ranges keep their meaning.

Verification: taskgraph validate (43 tasks), repo grep clean (single
exempt hit: the verification command's own search pattern in AGENTS.md),
cargo test, clippy --all-targets -D warnings, fmt --check, doc all green.
2026-11-04 20:28:00 +00:00

5.8 KiB
Raw Blame History

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
w4-router-collision Wave 4: ListenRouter token-collision pre-check (wave-3 gate booked item 4) completed
w3-tunnel-listen
single low isolated implementation

Description

Close the wave-3 gate's booked finding item 4 (review-wave-3 §Booked findings): a defensive ListenRouter token-collision pre-check. register silently replaces on token collision today; safety rests on the fabric's ADR-008 mint discipline (collision "can't happen" upstream). The gate's disposition: a fail-closed decline-the-listen-on-collision is a cheap wave-4 addition, following the same posture the gate landed inline for duplicate tunnel/forwarded op registration (install_forwarded_surface now refuses with AdapterError::ListenRegistration instead of trusting the silent-replace).

  • The pre-check: a register on an already-held token refuses (the listen declines fail-closed) instead of silently replacing the held route, so a collided mint can never steal a live adapter's arrival routes.
  • The error surface: reuse/extend the AdapterError::ListenRegistration class (decide + record, with a distinct variant only if the gate's variant can't carry the distinction honestly).
  • Wire row(s): the collision constructible (two registers on one token, with the test forcing what the fabric's discipline prevents) → the second refuses, the first route still serves (the steal is impossible, row-pinned); the existing arrival/cancel/teardown rows stay green.

Acceptance Criteria

  • ListenRouter::register refuses on token collision (fail-closed, no silent replace); the error surface decided + recorded
  • Wire row(s): the second register refuses, the first route still serves; existing rows green
  • cargo test (+ per-feature + --all-features), clippy ×2 (-D warnings), fmt, cargo doc --no-deps pass

References

  • tasks/implementation/review-wave-3.md §Booked findings item 4 (and §Minor issues, the install_forwarded_surface pre-check this mirrors)
  • src/tunnel_map.rs (the ListenRouter), src/error.rs (the AdapterError class), docs/architecture/decisions/ (the fabric's ADR-008 mint discipline, the upstream guarantee the pre-check defends in depth against)

Notes

Implementation (2026-10-29)

  • ListenRouter::register (src/tunnel_map.rs) now returns Result<(), String>: a register on an already-held token returns Err and never replaces the held route, so a collided mint can never steal a live adapter's arrival routes. The refusal detail carries the token + the steal posture.
  • Error surface (decided + recorded): the refusal surfaces as the request-time decline disposition (RequestDenied, reason token-collision), not a distinct AdapterError variant. The deciding fact: a global-request hook's Err is the taxonomy's only route into the request surface, and russh 0.65 propagates it with ? out of the packet loop, which kills the whole session, while the vanilla contract for a refused listen is RequestDenied. AdapterError::ListenRegistration honestly carries the construction-time class it always did (the doc in src/error.rs records the wire-time sibling); the reuse is the shared fail-closed registration posture, no new variant needed.
  • listen_request_dispatch (src/handler.rs): the register moved before the &mut u32 out-param write (a refused listen never writes the bound port); on refusal the path releases the listener cap, emits decline_global_request(row, "token-collision") (the uniform vocabulary, closed, with no dynamic fields), and tears the minted fabric listener down immediately in a spawned task (TunnelListener::close, the cancel path's mechanism; a refused registration never left a half state).
  • Wire row (tests/tunnel_mapping.rs a_collided_mint_is_refused_and_never_steals_the_live_route): the fabric double gained a mint-strategy construct (wire_fabric_with_listen_mint + MintStrategy::WithCollisions, where the first N mints return a fixed token, then the ADR-008 counter continues; the test forces exactly what the fabric's discipline prevents). The row pins, against a 2-count listener ceiling (the ceiling that admits the collided mint past the cap, since the cap row's shape would decline at the cap before the register, and it makes the release observable): the first listen registers normally, the colliding mint's listen answers RequestDenied, its bound endpoint never serves again (teardown observable, per the cancel row's bounded-retry shape), the listener count was released (a fresh port = 0 mint is admitted and registers, with its success reply proves the shape), and the first route still serves end to end (an arrival opens the echoed forwarded-tcpip and the bytes ride); the steal is impossible, row-pinned.

Summary

Completed. The ListenRouter token-collision pre-check landed fail-closed (register refuses, never replaces) with the error-surface decision recorded: the wire-time collision is the decline disposition (token-collision / RequestDenied), not the taxonomy, since an Err would kill the whole russh session, while AdapterError:: ListenRegistration keeps its construction-time class. The refusal path is the full fail-closed shape (cap release, uniform decline event, and immediate fabric teardown, so no half state). Row-pinned through a mint-strategy construct on the fabric double: second register refuses, first route still serves end to end, refused endpoint never serves, count released. Full verification set green: cargo test (default + git/session/tunnel/sftp + --all-features), clippy ×2 -D warnings, fmt, cargo doc --no-deps; existing arrival/cancel/ teardown rows unchanged and green.