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.
227 lines
11 KiB
Markdown
227 lines
11 KiB
Markdown
---
|
|
id: w3-decline-tracing
|
|
name: "Wave 3: decline-path tracing (the wave-2 gate's booked finding)"
|
|
status: completed
|
|
depends_on: [w3-seam-config]
|
|
scope: narrow
|
|
risk: low
|
|
impact: component
|
|
level: 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, so 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; therefore 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**, with the deciding context on every
|
|
decline being row + feature build + channel id (plus 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), with
|
|
the stubs' events carrying 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, and the
|
|
crate's events must be safe-by-default at every level).
|
|
|
|
A single small cross-cutting pass over the landed
|
|
handler/dispatch sites with the vocabulary, so the three mapping branches never land
|
|
inconsistent events.
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [x] The family tracing posture read and recorded; the event
|
|
vocabulary established (row + build + channel + decline class)
|
|
and used at every decline site in `src/`
|
|
- [x] Serve-path skeleton events in place (start/ended/failure) so the
|
|
mapping tasks' spawned paths inherit shaped rows
|
|
- [x] The safety posture recorded (no secrets, no username; what
|
|
identity detail may appear decided)
|
|
- [x] A test pins the event presence at the decline sites (or the
|
|
doc-test convention the family uses, decided and recorded)
|
|
- [x] `cargo test`, `cargo clippy --all-targets -- -D warnings`,
|
|
`cargo fmt --check`, `cargo doc --no-deps` pass
|
|
|
|
## 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 this task reads and
|
|
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`): free `tracing::{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 = …`); levels `debug` for
|
|
structural observations, `warn` for recoverable failures, `error`
|
|
for abnormal pump terminations.
|
|
- **alkcall** (`protocol/*`, `channels/*`): the widest use, with the
|
|
same fields convention (`channel_id`, `request_id`, `error = %e`),
|
|
component-prefixed messages (`"channel client: …"`, `"open wrapper:
|
|
…"`), `debug`/`warn` only.
|
|
- **alkhttp** (the closest structural analog): `warn`/`error` with
|
|
`error = %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
|
|
order `session, git, tunnel, sftp`), so a captured event is
|
|
self-describing per build; `sftp` ships 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, and `auth.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"`
|
|
(the subsystem rows under 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,
|
|
because `ChannelId` is not publicly constructible, so the helpers take the
|
|
raw u32 via `ChannelId::number()`); the auth class and the global
|
|
`-R` listen forms carry none (a separate `decline_global_request`
|
|
helper, since global requests have no channel).
|
|
- **`exec_route`**, only on `exec_request`-row events: the parse
|
|
route (`"git"` | `"pipe"`), so a git row's decline is
|
|
distinguishable from a pipe row's in combined builds. A `None` route
|
|
field is not recorded at all by `tracing` (discovered by the unit
|
|
rows; recorded in the module docs). The `message` field rides first
|
|
(the `event!` macro's shape, the unit rows pinning the field
|
|
order).
|
|
|
|
Levels: every decline and the lifecycle's started/ended emit at
|
|
`debug`, since 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, which is 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 and 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, leaving 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, with 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, since 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`), because
|
|
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.rs` unit rows, thread-local
|
|
`with_default`): exact field sets and order (message first), the
|
|
`exec_route` presence semantics, the no-channel auth shape, the
|
|
lifecycle messages, the `warn` failure arm, and the
|
|
cfg-derived `feature_build` string.
|
|
- Wire-level (`tests/observe.rs`, the loopback harness drives the
|
|
real wire; capture installed up front via the mount helpers, since
|
|
a lazily-installed global loses the earliest emissions): presence by
|
|
field-matching at every decline site across the builds: x11 open,
|
|
`-R` listen (global), the fingerprint-unresolved and auth_none
|
|
dispositions (driven on fresh mounts, since 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's `connect-failed`,
|
|
the cap-exceeded row (zero-cap limits must ride the *server* config
|
|
(`serve_config`), not just the client's `Limits`), and the
|
|
username/fingerprint safety scan. Build-specific rows follow the
|
|
`dispatch_core.rs` per-build cfg convention and were verified in
|
|
the default, `git`, `session`, `tunnel`, and `--all-features`
|
|
builds.
|
|
|
|
### 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` ✓. |