The wave-2 gate's booked finding: observability on the decline paths. `tracing` rides the manifest per the ADR-002 toolkit but nothing in src/ emitted — a decline's "reason" was only the routing decision documented per build. - src/observe.rs: the event vocabulary, adopting the family posture read from alktty/alktunnels/alkcall/alkhttp (free tracing fns, message-last lowercase, flat fields; no info!/trace!, no macros, no spans): feature_build + row + channel_id + decline_class (open/request/serving/auth) + reason + exec_route (exec rows); declines at debug, the serving-path failure arm the vocabulary's one warn - handler.rs: every decline site emits — auth dispositions (non-ed25519 / fingerprint-unresolved / the four unimplemented methods), open-level declines (unfeatured / never-feature / cap-exceeded / tunnel connect-failed stub), request-level capability declines (exec route-aware, subsystem, env, x11, agent, the -R listen forms), and the seam-stub serving declines with the spawned-path lifecycle (started / ended / failed — the shape the wave-3 mappings inherit) - tests/observe.rs: the event-presence pin — a hand-rolled global capture subscriber (the recorded test convention; no event-capture precedent exists in the family), field-matching rows across the default/git/session/tunnel/--all-features builds, and the safety scan (no username, no fingerprint ever in an event) - helper-level exact shapes pinned in src/observe.rs unit rows Verification: cargo test (default, --features git/session/tunnel, --all-features all green), clippy x2 --all-targets -D warnings, fmt --check, cargo doc --no-deps 0 warnings, taskgraph validate.
11 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| w3-decline-tracing | Wave 3 — decline-path tracing (the wave-2 gate's booked finding) | completed |
|
narrow | low | component | implementation |
Description
Land the wave-2 review gate's booked finding: observability on the
decline paths. tracing rides the manifest per the ADR-002 toolkit but
so far nothing in src/ emits — a request-level decline's "reason" is
today only the routing decision documented per build. The real serving
paths land in this wave's mapping tasks, where serve/decline tracing
matters — so the event vocabulary lands first and the mappings
inherit it.
- The family-convention check first — before choosing the event
shape, read the family's established tracing posture (alktty /
alkgit / alktunnels / alkcall
src/: levels, event/field naming, what gets emitted on error and decline paths) and adopt it — no alkssh-private dialect; record what was read and chosen. - The event vocabulary — the deciding context on every decline: row + feature build + channel id (+ the decline class — open-level vs request-level vs serving-path, and where applicable the failure shape carried). The mapping tasks then emit at their sites.
- Instrument what exists now — the open-level declines
(unfeatured / never-feature / cap-exceeded), the request-level
capability declines (
subsystem/env/x11/agent, the unfeatured-parse routes), the seam-stub serving declines, and the serve-path skeleton (spawned path started / ended, failure) — the stubs' events carry into the mapping tasks' replaced paths. - Safety posture (conventions 6, OQ-03): no secret material ever enters an event, never the username; identity/fingerprint exposure on decline events is decided here against the family posture and recorded (auth.md: log exposure is deployment policy — the crate's events must be safe-by-default at every level).
Small, cross-cutting unit — one pass over the landed handler/dispatch sites with the vocabulary, so the three mapping branches never land inconsistent events.
Acceptance Criteria
- The family tracing posture read and recorded; the event
vocabulary established (row + build + channel + decline class)
and used at every decline site in
src/ - Serve-path skeleton events in place (start/ended/failure) so the mapping tasks' spawned paths inherit shaped rows
- The safety posture recorded (no secrets, no username; what identity detail may appear decided)
- A test pins the event presence at the decline sites (or the doc-test convention the family uses — decide + record)
cargo test,cargo clippy --all-targets -- -D warnings,cargo fmt --check,cargo doc --no-depspass
References
- docs/plans/implementation.md §Wave 2 (the booked finding, review-wave-2 §Notes)
- docs/architecture/mapping.md §Wire decline reasons (the two decline classes), §Decline matrix
- docs/architecture/auth.md §Dispositions (log exposure posture), docs/architecture/decisions/002-russh-pin.md (the toolkit)
- sibling
src/tracing usage — the convention read this task adopts
Notes
The family posture read (2026-10-28)
Read before choosing the event shape (the task's first bullet), across
the family's src/:
- alktty (
adapter.rs,session.rs,channels.rs,local/pipe.rs,local/pty.rs): freetracing::{debug, warn}imports; message-last lowercase sentences with a component prefix ("tty: backend stdin write failed: {e}"); interpolation in messages, no spans. - alktunnels (
producer.rs,consumer.rs,params.rs): the same free-fn posture, plus structured fields where identity exists (listen = …,error = %e,reason = …); levelsdebugfor structural observations,warnfor recoverable failures,errorfor abnormal pump terminations. - alkcall (
protocol/*,channels/*): the widest use — the same fields convention (channel_id,request_id,error = %e), component-prefixed messages ("channel client: …","open wrapper: …"),debug/warnonly. - alkhttp (the closest structural analog):
warn/errorwitherror = %e("channels session ended"). - alkgit: no tracing at all at this writing.
Never observed: info!/trace! events, subscriber setup in library
code, macro or span wrappers. Adopted verbatim in src/observe.rs:
free tracing fn calls, message-last lowercase with the
"decline: …" / "serving path …" phrasing, flat per-event fields.
The event vocabulary (landed in src/observe.rs)
feature_build— the compile-time feature set as a stable string ("none","git","session+git", …; features join in the ordersession, git, tunnel, sftp), so a captured event is self-describing per build;sftpships in the string even though it gates no route (it is part of what the assembly deployed).row— the greppable wire row: the channel type for open-level ("session","direct-tcpip","x11", …), the request name for request-level ("pty_request","tcpip_forward", …), the handler hook name for auth ("auth_publickey","auth_none", …).decline_class—"open"|"request"|"serving"|"auth": the two wire decline classes (mapping.md§Wire decline reasons), plus the serving-path class (this wave the seam stub's decline is the serving path; the mappings emit the same class from their real spawned paths) and the auth class (the ADR-004 disposition declines — added by this task: the auth hooks are decline sites too, andauth.md's dispositions are the crate's auth-decline contract).reason— the decline cause, mirroring the architecture's own classifications:"unfeatured","never-feature"(ADR-003 §5),"cap-exceeded","connect-failed"(tunnel stub / ADR-006 decision 1),"seam-stub"(session-row stubs),"unimplemented"(subsystem rows — OQ-13 — and the wave-3 listen forms),"declined-by-design"(env — OQ-09), and on the auth class"method-unimplemented"/"cert-unsupported"/"non-ed25519"/"fingerprint-unresolved".channel_id— the wire channel id (u32), on every decline of a channel (notably: the event's id is russh's server-side id —ChannelIdis not publicly constructible, so the helpers take the raw u32 viaChannelId::number()); the auth class and the global-Rlisten forms carry none (a separatedecline_global_requesthelper — global requests have no channel).exec_route— only onexec_request-row events: the parse route ("git"|"pipe"), so a git row's decline is distinguishable from a pipe row's in combined builds. ANoneroute field is not recorded at all bytracing(discovered by the unit rows; recorded in the module docs). Themessagefield rides first (theevent!macro's shape — the unit rows pin the field order).
Levels: every decline and the lifecycle's started/ended emit at
debug — a decline is the door's normal, honest posture (vanilla
clients probe unfeatured rows routinely), matching the family's
structural-observation level. Only [serving_path_failed] is warn
(a serving path that could not complete its wire reporting is a real
failure — the family's recoverable-failure level).
The serving-path skeleton
src/serve.rs stays the mechanics only (spawn + exit-report); the
lifecycle events hang off the spawned task's body in
handler.rs's seam_stub_decline: started (the spawned task's first
action, so "started" is the task's honest start, not the hook's spawn
call) → the serving-path decline (class serving, reason seam-stub)
→ ended — or failed (warn, with the russh protocol error) when a
report send could not complete. The wave-3 mapping branches
(w3-git-mapping, w3-tty-session, w3-tunnel-direct) inherit the
shaped rows by emitting the same helpers from their real serving tasks.
The safety posture (conventions 6, OQ-03 — recorded)
No secret material ever enters an event; never the username;
the fingerprint stays out too. Structurally enforced, not recalled:
the vocabulary helpers take only &'static str values and the wire
channel id — no parameter a username, command string, env name/value,
repo path, key, or fingerprint could ride. The reason codes carry
the decline's cause class, and the deciding context is the row +
build + channel — deliberately not the identity behind the wire
attempt. This is the recording of auth.md §Dispositions' standing
rule at the crate level: log exposure is deployment policy, so the
crate's events are safe-by-default at every level — the fingerprint
(and any identity detail) is a deployer-side computation over its own
logs, never crate-emitted. If a future decline site needs
request-argument detail (e.g. the git grammar's decline reasons in
w3-git-mapping), it must add an explicitly-vocabulary'd field and
record its safety reasoning — dynamic strings do not ride
src/observe.rs by default. The wire rows pin the posture by
scanning every captured event: no username string, no SHA256:
fingerprint, no user*-named field ever appears.
The test convention (decided + recorded)
The family has no event-capture precedent to adopt — alktunnels'
tracing-subscriber dev-dep drives fmt output, not assertions. The
convention these rows pin: a hand-rolled minimal
tracing::Subscriber capturing events into a shared buffer,
installed once as the global default (tests/observe.rs) — the
server side runs on spawned tasks, so a thread-local
with_default capture cannot see them, and a global capture needs no
new dependency. Two levels of pinning:
- Helper-level (
src/observe.rsunit rows, thread-localwith_default): exact field sets and order (message first), theexec_routepresence semantics, the no-channel auth shape, the lifecycle messages, thewarnfailure arm, and the cfg-derivedfeature_buildstring. - Wire-level (
tests/observe.rs, the loopback harness drives the real wire; capture installed up front via the mount helpers — a lazily-installed global loses the earliest emissions): presence by field-matching at every decline site across the builds — x11 open,-Rlisten (global), the fingerprint-unresolved and auth_none dispositions (driven on fresh mounts — a second auth exchange on an already-authenticated session is not a supported russh surface, as the probe discovered), the unfeatured session/pipe/pty rows, the git seam-stub's full lifecycle, the tunnel stub'sconnect-failed, the cap-exceeded row (zero-cap limits must ride the server config —serve_config, not just the client'sLimits), and the username/fingerprint safety scan. Build-specific rows follow thedispatch_core.rsper-build cfg convention and were verified in the default,git,session,tunnel, and--all-featuresbuilds.
Verification (all green)
cargo test default build 110 rows across 7 binaries + doctests ·
cargo test --all-features ✓ · cargo test --features git/session/tunnel
✓ (each build runs its decline rows) ·
cargo clippy --all-targets -- -D warnings ✓ ·
cargo clippy --all-features --all-targets -- -D warnings ✓ ·
cargo fmt --check ✓ · cargo doc --no-deps 0 warnings ·
taskgraph validate ✓.