Gate reading (commits b90e89b..196289f) against the architecture line by line: ADR-001 mount contract, ADR-002 Config construction, ADR-003 feature gates, ADR-004 dispositions, ADR-006 constraints, mapping.md skeleton decline matrix per build, bounded resources, and the harness — all confirmed. All four flagged interpretations confirmed as recorded. Fixed inline before committing (task-file hygiene only): - w2-loopback-harness, w2-dispatch-decline-core: acceptance boxes checked once verified against the landed rows/runs - w2-bounded-resources: stale placeholder lines removed - review-wave-2: verdict recorded Booked finding (next wave's decomposition): decline-path tracing/observability — the reason a given wire decline carried is currently docs-only (tracing unused in src/). Verification: cargo test 104 rows green; --all-features green; clippy ×2 -D warnings green; fmt clean; cargo doc 0 warnings; taskgraph validate 19 tasks.
12 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| w2-dispatch-decline-core | Wave 2 — parse-only dispatch + the fail-closed decline core | completed |
|
moderate | medium | phase | implementation |
Description
Land the dispatch skeleton and the fail-closed decline core (ADR-006 as amended, ADR-003) — the routing decisions and the declines; the family-seam serving paths are wave-3 material and arrive as clean declining stubs this task shapes the routes for.
- Channel-open gates (the
channel_open_*hooks):sessionchannels open only where a session-serving feature exists (session∨git— the parse path lives inside session channels);direct-tcpip/forwarded-tcpip/direct-streamlocalgate on thetunnelfeature;x11andauth-agentalways decline (never feature-gated — ADR-003 §5); every other/unimplemented channel type declines. Unfeatured → decline at open with a clean wire reason — never a half-open channel. - Parse-only session dispatch (ADR-006 point 3 as amended):
exec_requestparses first — a git-parseable command routes to the git row, anything else to the session pipe row;pty_request/shell_requestroute to the session rows. The git-parseability probe here is skeleton-level (service prefix + path shape); the full grammar (quoted/unquoted paths, service-only → decline-with-reason, per-length limits) is wave-3's git task, which replaces this probe. Parsing happens before dispatch and before any resource allocation — decline first, alloc never (a leaked channel alloc is a capability leak; the POC-3 stage-A rule). - Request-level declines:
subsystem_request— thesftpname declines-with-reason (the OQ-13 placeholder, folded here server side); git service names decline-with-reason (a subsystem request carries only a name, never a repo id); everything else declines.env_requestdeclines unconditionally (OQ-09 — no allowlist ships). - Routing stubs: the git row and the session rows (pty/shell, pipe-exec) route to stubs that decline cleanly at the family-seam boundary — the seams land in wave 3; the stub shape is documented as skeleton behavior so the routing decisions are contract-tested now, not after the mappings exist.
- Wire decline reasons are shaped here (the wave-1 error-model
note's reservation): channel-open failure codes vs
channel_failureper decline class, chosen for vanilla-client observability (POC-1 §5: channel-scoped decline, auth intact, no hangs). - Contract tests through the loopback harness, per feature build:
the skeleton rows of the decline matrix (
mapping.md§Decline matrix) — none-build declines everything; git-build routes git-parseable exec (stub at the seam) and declines pty/shell and plain exec; session-build declines git-parseable exec (the git parse is its gate) and serves its rows only to the stub; tunnel rows decline (the route-1 bridge is wave 3) even with the feature on; x11/auth-agent decline in every build. The full per-build matrix suite is wave-5's; this task tests the rows it lands.
Acceptance Criteria
- Every
channel_open_*hook gates per the feature graph; x11/auth-agent decline in every build; unfeatured types decline at open with clean wire reasons exec_requesttakes the parse path (git-parseable → git row, else → session pipe row) before any allocation; request-less session channels decline as unfeatured per ADR-006subsystem_requestdeclines-with-reason forsftp(OQ-13 placeholder) and git service names;env_requestdeclines unconditionally (OQ-09)- Decline-first-alloc-never holds structurally (declines precede any channel/resource allocation on every path)
- Feature-gated code paths exercised per build (
cargo test --features git/session/tunnelrows +--all-features); the skeleton decline-matrix rows green through the harness cargo test,cargo clippy --all-targets -- -D warnings,cargo clippy --all-features --all-targets -- -D warnings,cargo fmt --checkpass
References
- docs/architecture/decisions/006-mapping-bridge-and-dispatch.md (points 2–3; the amendment — parse-only)
- docs/architecture/decisions/003-feature-graph.md (the feature graph; §5 never-features)
- docs/architecture/mapping.md §Session-channel dispatch, §Request-level rows, §Decline matrix
- docs/plans/implementation.md §Wave 2 (the dispatch bullet)
- docs/research/poc-3-mapping-bakeoff.md §stage A (the decline matrix evidence; decline-first-alloc-never)
Notes
src/dispatch.rs(new): the parse-only dispatch core — theExecRoutedecision (Git/Pipe), the skeleton exec-parse probe (classify_exec: exactgit-upload-pack/git-receive-packservice prefix + space + path-shaped argument — leading'or/; everything else → the pipe row), and the two wire-decline reason shapers (unfeatured_open_decline→AdministrativelyProhibited,tunnel_seam_stub_decline→ConnectFailed). Seven probe unit rows pin the POC-3 stage-A grammar facts the skeleton carries. The full grammar (quoted/ unquoted paths, per-length limits, service-only decline-with-reason unknown-service rows) is wave-3's to replace it with.src/handler.rs: everychannel_open_*hook gates per the feature graph (session open:session∨git; the three tunnel opens:tunnel; x11: always declines). Request hooks:exec_requestparses first then routes on the feature graph;pty/shellroute undersession;subsystem_requestdeclines every name (sftp = the OQ-13 placeholder,git-*= no repository id in a name-only request — ADR-006 decision 2);env_requestdeclines unconditionally (OQ-09);x11_request/agent_requestdecline (ADR-003 §5); the four listen requests decline (the flow is wave 3; never advertise a listen that cannot serve). Decline-first- alloc-never holds structurally: no hook allocates before its decline; a confirmed session open accepts and drops the channel handle (nothing is held this wave — the store-and-take handoff is wave-3 mapping material, and the routing decisions around it are already pinned by the seam stubs).- Wire decline reasons (the wave-1 error-model reservation, now
spent): open-level declines carry channel-open failure codes —
unfeatured and never-feature rows answer
AdministrativelyProhibited(1) is the honest class: without the feature there is no capability to distinguish; the tunnel seam stub answersConnectFailed(2), the explicit reject shape ADR-006 decision 1 pins for failed fabric opens (accept only after the fabric open succeeds; the skeleton never opens the fabric). Request-level declines carrychannel_failure— the SSH request vocabulary has no failure text, so the "reason" is the routing decision, documented per build. Within request declines, two shapes: a capability decline answers failure and leaves the channel usable (POC-1 §5 posture, proven live by the channel-alive rows); a serving-path decline (the seam stubs) answers failure then closes the channel (vanilla clients never hang on a row that will spawn next wave). - russh 0.65 facts coded around: only
agent_requestand the global requests auto-reply from the handler'sbool; every other request hook must callsession.channel_failureitself. The bareauth-agent@openssh.com(andforwarded-streamlocal@openssh.com) channel types have no handler hook — russh rejects those opens inline (administratively prohibited); that is the decline the never-feature contract needs but it is an inherited posture flagged in the handler docs (pin-bump change would appear as accepted-then-dropped — upstream note).forwarded-tcpiphas the same gate/stub code shape as its tcp sibling but no client-side open helper (the server opens it toward the client), so its wire rows are wave-3 acceptor material. - Harness: the helper vocabulary extended (tunnel/x11 opens; pty/
env/x11/agent-forward requests;
tcpip_forward), discharging the loopback-harness task's recorded expectation that the request helpers' live rows arrive here. One vehicle row (authenticated_session_serves_and_declines_with_an_observed_reason) recorded the skeleton decline-only behavior; the dispatch core makes session opens serve under their features, so that row is now cfg-pinned to the featureless builds it speaks about (itsChannelOpenFailure-reason observation remains the none-build's proof). - Request-less session channels (ADR-006 as amended): parse-only means nothing serves before a request arrives (the SOCKS-detect is retired); a request-less channel is served by nothing and dies at the wall clock. The wire-observable rows are the unfeatured opens (none/tunnel builds decline session channels at the open); the no-detect posture is structural (no serving path exists outside the request hooks).
Summary
Landed the parse-only dispatch + the fail-closed decline core
(w2-dispatch-decline-core) in three files plus the harness:
src/dispatch.rs— theExecRoutedecision, the skeleton exec-parse probe (service prefix + path shape; wave-3 grammar replaces), the wire-reason shapers (AdministrativelyProhibitedfor unfeatured/never-feature opens,ConnectFailedfor the tunnel seam stub), 7 probe unit rows.src/handler.rs— thechannel_open_*gates (session ∨ git; tunnel; x11/auth-agent never), the parse-onlyexec_requestdispatch, the session-row routes, the request-level declines (subsystem all-names with the sftp/git named classes, env, x11, agent), the four listen-request declines, and the named seam stubs (git_row_stub,pipe_row_stub,pty_row_stub,shell_row_stub,tunnel_row_stub) — the wave-3 replacement points, each documented with its family seam. Module docs now carry the decline-core contract (gates, decline classes, wire reasons, decline-first-alloc-never, the parse-only posture, the inherited-auth-agent-open upstream note).tests/dispatch_core.rs— the skeleton decline-matrix rows per feature build, 26 rows total, each with aContract stamp:: shared rows in every build (x11 open,tcpip-forwarddecline, x11/agent requests where sessions open); none-build (everything declines at open); git-only (sessions open, git exec → seam-stub failure+close, plain exec/unknown services → pipe-row decline with channel alive, pty/shell/subsystem/env/capability declines, tunnel unfeatured); session-only (git-parseable exec declines as gate, stub rows failure+close, capability declines, tunnel unfeatured); tunnel-only (session opens decline, featured tunnel rows →ConnectFailedstubs); all-features (routes hold with the full graph — git row, pipe row, pty/shell stubs,ConnectFailedstubs, capability declines).tests/common/harness.rs— the declined-vocabulary helpers (tunnel/x11 opens, pty/env/x11/agent requests, listen requests), module doc updated to record the discharge of thew2-loopback-harnessnote.
Verification (all green): cargo test (91 rows across 7 binaries) ·
cargo test --features git/session/tunnel/sftp --test dispatch_core
(each build's rows) · cargo test --all-features ·
cargo clippy --all-targets -- -D warnings ·
cargo clippy --all-features --all-targets -- -D warnings ·
cargo fmt --check · cargo doc --no-deps (no warnings). One
pre-existing harness row pinned to the skeleton's decline-only open
behavior was cfg-gated to the featureless builds it describes.