Files
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

158 lines
7.8 KiB
Markdown

---
id: w1-config
name: "Wave 1: the config surface (serve-config, dial-config, Limits)"
status: completed
depends_on: [w1-manifest, w1-key-sources, w1-host-key-policy]
scope: moderate
risk: low
impact: project
level: implementation
---
## Description
Land the pure configuration types both directions implement against,
the shapes every later wave's constructors take:
- **Serve-config** (server direction, `mapping.md`/`overview.md`), the
assembly-supplied serving configuration: the OQ-08 fields
(**shell argv** and **exec wrapper argv**, each `Option<Vec<String>>`;
when unset, its row declines with a clean error, never a fallback,
no crate default; the wrapper form is platform-neutral), the
`IdentityProvider` handle (`Arc<dyn IdentityProvider>`, which the
assembly injects; the crate never resolves identities itself), the
host-key source handle (the wave-1 key-source seam), the tunnel
`ChannelClient` handle (the route-1 bridge's family-fabric entry,
held as an `Option`; when absent, tunnel rows decline even under
the feature), and the `Limits`. `cwd`/`env` in `NegotiateRequest`
are assembly-supplied serve-config values, never wire-derived
(OQ-09 posture).
- **Dial-config** (client direction, `client.md` §The dial surface):
address, key source (the wave-1 seam; never static keys, never env
vars), the `HostKeyPolicy` handle, and the bounded-resource knobs:
`keepalive_interval`/`keepalive_max` (15 s / 3 default posture per
the reverse-runner evidence), `inactivity_timeout`, window/packet
knobs.
- **`Limits`**: the bounded-resources budget model (`overview.md`'s
table): the in-crate max-concurrent-channels cap, exec-parse length
caps (consumed by the wave-3 grammar), and the russh knob mapping
with documented default postures (`max_auth_attempts` default 10,
sized for the counting `none`-probe per ADR-004 decision 6;
`window_size`/`maximum_packet_size`/`channel_buffer_size` set;
`inactivity_timeout` set, assembly-tunable). Every session carries
it; no unbounded loops, no unbounded buffers.
Types only: no russh `Config` construction (wave 2), no dialing
(wave 4). The OQ-08 unset-declines semantics live in the type shape
(`Option` fields + doc contracts), not in construction-time
validation.
## Acceptance Criteria
- [x] Serve-config carries the OQ-08 fields as `Option` with
unset-declines doc contracts; no crate default shell/wrapper
- [x] `IdentityProvider`, host-key source, and `ChannelClient` are
injected handles; the crate holds no identity/trust policy of
its own
- [x] Dial-config carries key source + `HostKeyPolicy` + the
keepalive/inactivity/window knobs with the documented defaults
- [x] `Limits` carries the in-crate caps (channels, exec-parse) and
the russh knob defaults (`max_auth_attempts` 10, keepalive
15 s / 3 client posture)
- [x] Unit tests cover default construction and the `Option`-field
semantics
- [x] `cargo test`, `cargo clippy --all-targets -- -D warnings`,
`cargo fmt --check` pass
## References
- docs/architecture/overview.md §Public surface, §Bounded-resources
policy
- docs/architecture/mapping.md (OQ-08 rows, the data-plane/limits
section)
- docs/architecture/client.md §The dial surface
- docs/architecture/open-questions.md (OQ-08, OQ-09 resolutions)
- docs/architecture/decisions/004-auth-dispositions.md (decision 6:
the `max_auth_attempts` default)
## Notes
> Landed `src/config.rs` (`ServeConfig`, `DialConfig`, `Limits`,
> four posture constants) + lib.rs re-exports. Design decisions
> pinned against the docs and the alkcall/russh 0.65 source:
>
> - **`Limits` is the single budget model both directions carry**
> (`Copy`, plain tuning data): the in-crate caps
> (`max_concurrent_channels` 64, `max_exec_command_len` 8192, with
> numbers chosen here and documented as assembly-tunable posture
> constants) plus the russh-knob mapping (`max_auth_attempts` 10
> per ADR-004 decision 6; `window_size` 2 MiB / `maximum_packet_size`
> 32 KiB / `channel_buffer_size` 100, russh's own defaults kept;
> `inactivity_timeout` 600 s carried as plain `Duration` so the
> "set (assembly-tunable)" posture is structural, with wave 2/4
> mapping it to russh's `Option` as always-`Some`;
> `keepalive_interval`/`keepalive_max` Some(15 s)/3 riding inside as
> the client-direction posture, satisfying the dial-config
> criterion without a duplicate knob set). russh's rekey `Limits`
> stay russh-enforced at their RFC-4253 defaults and are not
> carried (documented; wave-2 material).
> - **Dial-config carries the knobs via its `Limits` field**: the
> criterion's "keepalive/inactivity/window knobs with the
> documented defaults" read as the budget model riding the config;
> the accepted criterion on `Limits` itself pins that placement,
> so a second knob set on `DialConfig` would have duplicated state.
> - **No `Default` for the two configs**: the required handles
> (`Arc<dyn IdentityProvider>`,
> `Arc<dyn HostKeySource>`, `Arc<dyn KeySource>`,
> `Arc<dyn HostKeyPolicy>`) have no crate-defaultable shape
> (identity resolution and trust are injection-only, per the
> criterion); `ServeConfig::new`/`DialConfig::new` take them, and
> every tunable is a `pub` field + `Limits::default()`.
> - **The OQ-08 fields are plain `Option<Vec<String>>` `pub` fields
> + doc contracts**; no construction-time validation (the
> `error.rs` no-config-error contract). Two request-time check
> helpers exist (`shell_configured`/`exec_wrapper_configured`,
> plus `tunnel_configured`) for the wave-2 dispatch to call,
> reading the `Option`, never validating.
> - **`cwd`/`env` (OQ-09 posture) carried on `ServeConfig`** as
> `Option<PathBuf>` / `BTreeMap<String, String>` (empty defaults;
> never wire-derived; doc contract).
> - **`channel_client` is not feature-gated**: alkcall is a base
> dependency and a field presence is not a capability; the `tunnel`
> feature gate applies at wave 2 on top (absent → decline even
> under the feature; unfeatured → decline too). Doc contract
> records both gates.
> - Verified the alkcall 0.10 surfaces in-source (registry):
> `core::auth::IdentityProvider` is a sync,
> `Send + Sync + 'static` trait (dyn-compatible with no
> async-trait help on alkssh's side; `ChannelClient` and the
> `NoopIdentityProvider` test fixture ride
> `alkcall::channels::client` / `alkcall::core::auth`), and the
> russh 0.65.0 knob names (`window_size`, `maximum_packet_size`,
> `channel_buffer_size`, `inactivity_timeout`,
> `keepalive_interval`, `keepalive_max`, `max_auth_attempts`) match
> this module's fields one-for-one.
## Summary
> Config surface landed: `src/config.rs` with `Limits` (in-crate
> caps: `max_concurrent_channels` 64, `max_exec_command_len` 8192;
> russh knobs: `max_auth_attempts` 10, window 2 MiB, packet 32 KiB,
> buffer 100, inactivity 600 s, keepalive Some(15 s)/3, with every
> number
> a named posture constant or documented ADR anchor), `ServeConfig`
> (OQ-08 `shell_argv`/`exec_wrapper_argv` as unset-declines
> `Option<Vec<String>>`, OQ-09 `session_cwd`/`session_env`,
> injected `IdentityProvider` + `HostKeySource` + optional
> `ChannelClient`, `limits`), and `DialConfig` (address,
> injected `KeySource` + `HostKeyPolicy`, `limits` with the
> keepalive/inactivity/window posture riding inside). No
> construction-time validation, no crate defaults for the unset
> rows, no `Default` for the configs (handles only). Tests: defaults
> carry the documented postures; `Option` fields flip independently;
> handles hold by `Arc::ptr_eq`; the dial's key source and policy
> stay fail-closed through the config. Verified: `cargo test` (36),
> `cargo test --all-features`, `cargo clippy --all-targets
> -- -D warnings`, `cargo clippy --all-features --all-targets
> -- -D warnings`, `cargo fmt --check` all pass; `taskgraph
> validate` clean.