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.
158 lines
7.8 KiB
Markdown
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. |