Files
alkssh/tasks/implementation/w3-seam-config.md
T
glm-5.3-flash 270365ea4c feat(config): land the wave-3 per-feature family-seam handles (w3-seam-config)
- ServeConfig gains cfg-gated SessionSeam / GitSeam Option fields
  (whole-Option struct per seam — the shape decision recorded in the
  task notes): unset → the owned rows decline even though the feature
  is on, no construction-time validation
- SessionSeam carries the alktty backends map + backend selector +
  ownership provider; GitSeam carries the alkgit backend quartet +
  caller-owned pipeline semaphore + push_options_negotiated
- optional deps alktty 0.8.1 / alkgit 0.1.0 wired feature-to-dep
  (pins verified against crates.io: licenses, MSRV, shared alkcall
  resolution); gix-hash 0.28 as dev-dep only (alkgit does not
  re-export ObjectId for the trait doubles)
- per-feature construction/unset-declines/Debug rows in config.rs;
  accessors session_seam_configured / git_seam_configured; docs state
  what each seam handle carries

Verified: cargo test (per-feature + --all-features), clippy ×2
(-D warnings), fmt, cargo doc --no-deps, cargo tree feature isolation
(git-build: no alktty; session-build: no alkgit), taskgraph validate
2026-10-28 16:12:03 +00:00

198 lines
10 KiB
Markdown

---
id: w3-seam-config
name: Wave 3 — serve-config seam handles (alktty/alkgit per-feature)
status: completed
depends_on: [review-wave-2, w1-config]
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Extend [`crate::ServeConfig`] with the per-feature family-seam handles
the wave-3 mappings consume. This is wave 3's first *course
correction* to the wave-1 config surface — the assembly-supplied
composition inputs were never carried (mapping.md's seam-entries table
names them inside the seam signatures; the wave-1 config bullet
pre-dates that reading), and the mappings cannot call
`drive_session[_pre_negotiated]` / `serve_duplex_resolved` without
them. Landing them here, before the mapping tasks, keeps the
un-set-declines posture structural and the review surface unified:
- **The `session` seam handle** (feature-gated), unset → the session
rows decline even with the feature on (the decline matrix's "an
unset field declines its row though the feature is on" note,
config-driven). Contents, per alktty 0.8.1's actual
`drive_session[_pre_negotiated]` signature — decide the exact final
shape (flat `Option` fields vs an `Option`-struct per seam; record):
- the alktty backends map (`Arc<HashMap<String,
Arc<dyn TtyBackend>>>` — assembled by the assembly; alkssh never
touches a real backend, the `local` shapes stay alktty-side);
- the backend **selector** — the `NegotiateRequest.backend` string
alkssh writes (assembly-supplied, never wire-derived; decide and
record);
- the ownership provider `Option<Arc<dyn
alkcall::core::ownership::OwnershipProvider>>` (an alkcall type —
no new dep; pass-through to `drive_session_pre_negotiated`,
family ACL layer riding along).
- **The `git` seam handle** (feature-gated), unset → the git rows
decline even with the feature on. Contents, per alkgit 0.1.0's
`serve_duplex_resolved` signature: the four backend traits
(`GitRegistry` / `GitRefs` / `GitPackGen` / `GitPackIngest`), the
caller-owned server-wide `pipeline: Arc<tokio::sync::Semaphore>`,
and `push_options_negotiated: bool` (the assembler's config flag;
false in the v1 default).
- **Manifest** (the wave-1 rule: per-feature sibling deps land with
their first consuming wave — the config fields consume them first,
so they are not dead entries): optional deps
`alktty = "0.8.1"`, `alkgit = "0.1.0"` wired feature-to-dep
(`session = ["dep:alktty"]`, `git = ["dep:alkgit"]`) so a git-build
never compiles alktty and vice versa; verify the pins against
crates.io before committing (verified at decomposition: 0.8.1 /
0.1.0, both over the shared `alkcall 0.10` req → 0.10.1; MSRVs 1.88
≤ our 1.89; MIT OR Apache-2.0). **alktunnels is NOT landed here** —
the config carries no alktunnels types beyond the already-landed
`Arc<ChannelClient>`; its optional dep lands with its first
consuming code (`w3-tunnel-direct`).
No construction-time validation (the unset-declines contract lives in
the type shape, checked at request time by the mappings); the tunnel
handle stays `Option<Arc<ChannelClient>>` as landed; `Debug` impls and
the config module docs updated (the direction list now states what
each seam handle carries).
## Acceptance Criteria
- [x] `ServeConfig` gains the feature-gated seam handles (the final
shape decided and recorded); unset → the owned rows decline,
structurally carried, no construction-time validation
- [x] The optional deps + `dep:` feature wiring land with crates.io
pins verified; a git-build never compiles alktty and vice versa
(no code path outside the cfg gate touches the types)
- [x] The none-build's `ServeConfig::new` shape is unchanged;
feature-gated fields absent from featureless builds
- [x] Tests: the per-feature construction shapes, the
unset-declines-preserved rows, the `Debug` posture
- [x] `cargo test`, `cargo clippy --all-targets -- -D warnings`,
`cargo clippy --all-features --all-targets -- -D warnings`,
`cargo fmt --check`, `cargo doc --no-deps` pass
## References
- docs/plans/implementation.md §Wave 1 (config bullet), §Wave 3
- docs/architecture/mapping.md §The seams (the signature shapes),
§Session-channel dispatch (the unset note), §Decline matrix
- docs/architecture/decisions/002-russh-pin.md (the manifest rule),
docs/architecture/decisions/003-feature-graph.md (the feature graph)
- alktty 0.8.1: `src/adapter.rs:205-241` (`drive_session[
_pre_negotiated]`), `src/negotiation.rs:64-92` (`NegotiateRequest`)
- alkgit 0.1.0: `src/duplex.rs:1412-1426` (`serve_duplex_resolved`),
`src/backend.rs:31-162` (the four traits)
## Notes
### What landed (2026-10-28)
- `Cargo.toml`: optional `alktty = "0.8.1"` /
`alkgit = "0.1.0"` (both `default-features = false`), wired
`session = ["dep:alktty"]`, `git = ["dep:alkgit"]`. alktty's `local`
feature stays off — alkssh never touches a real backend; the `local`
shapes stay alktty-side. Feature-graph comment updated (wave-3
seam-config is the deps' first consumer; alktunnels still lands with
`w3-tunnel-direct`).
- `src/config.rs` (re-exported through `lib.rs`): the two seam-handle
types + the two cfg-gated `ServeConfig` fields + the cfg-gated
`session_seam_configured()` / `git_seam_configured()` accessors +
config-module docs updated (the direction list and the
unset-declines rule both state the seam handles now).
### Shape decision (the task's decide-and-record item): whole-`Option` struct per seam
`SessionSeam{backends, backend, ownership}` and
`GitSeam{registry, refs, packgen, ingest, push_options_negotiated,
pipeline}` held as `cfg`-gated `Option` fields
(`session_seam` / `git_seam`), each `Clone` (all payload types `Arc`
or plain data — the spawned serving tasks clone the seam), each with a
hand-rolled `Debug`. **Flat `Option` fields were rejected**: each seam
is consumed atomically by exactly one seam call
(`drive_session[_pre_negotiated]` / `serve_duplex_resolved`), so a
struct admits no half-supplied seam states (backends without a
selector; three traits without the fourth) that would all map to the
same all-or-nothing decline — the unset-declines contract reads as one
`Option` per seam, "unset → the owned rows decline even though the
feature is on" (the decline matrix's config-driven note), checked at
request time by the mappings, never at construction. The already-landed
tunnel handle stays the flat
`Option<Arc<ChannelClient>>` — a single-field seam needs no struct.
Naming: struct names carry the crate (the consuming mappings will see
it in the field docs); the fields carry the row vocabulary
(`session_seam` / `git_seam`, matching the decline matrix's row names).
### Pins verified against crates.io (2026-10-28, this task re-verified)
- `alktty 0.8.1`: published 2026-10-22, not yanked,
`MIT OR Apache-2.0`, `rust_version = "1.88"` ≤ our 1.89;
`alkcall ^0.10.1` — shares our `alkcall = "0.10"` resolution
(0.10.1), so no alkcall fork in the lockfile.
- `alkgit 0.1.0`: `MIT OR Apache-2.0`, `rust_version = "1.88"`;
`alkcall ^0.10` — same shared resolution.
- Signatures confirmed against the published crates (the registry
sources under `~/.cargo/registry/src/`): alktty's
`drive_session[_pre_negotiated]` (adapter.rs:205-241) takes
`(writer, reader, [req,] backends: Arc<HashMap<String, Arc<dyn
TtyBackend>>>, ownership: Option<Arc<dyn OwnershipProvider>>,
identity: Option<Identity>)` — the identity is the per-connection
resolved one, not config; alkgit's `serve_duplex_resolved`
(duplex.rs:1412) takes the four traits +
`push_options_negotiated: bool` + `pipeline: Arc<Semaphore>`.
`TtyBackend` / `NegotiateRequest` / `TerminalParamsWire` and
`GitRegistry/GitRefs/GitPackGen/GitPackIngest` are all re-exported
from the two lib roots.
### One course correction absorbed: `ObjectId` is not re-exported by alkgit
`GitPackGen::generate`/`common_haves` name `Vec<gix_hash::ObjectId>`
and alkgit's lib root does not re-export `ObjectId` — a backend-trait
doubled in tests has to name the type, so `gix-hash = "0.28"` (matching
alkgit's `^0.28` req; the same crate already in the lockfile) lands as
a **`[dev-dependencies]` entry only**, exercised exclusively by the
cfg-gated git test module. The lib dependency graph keeps the
feature-to-dep isolation below; the dev graph is universal by design
(tokio and friends are too). Flagged for `w3-git-mapping`: the
hand-rolled in-mem quartet will lean on this dev-dep for `ObjectId`
(same as this task's noop doubles, modeled on alkgit's own
`backend.rs:164-254` noop shapes — own code, not copied).
### Dependency isolation — verified structurally, not by assertion
`cargo tree -e features --features git`: zero `alktty` occurrences;
`cargo tree -e features --features session`: zero `alkgit`
occurrences; `--all-features`: both present. Combined with the cfg
gates (all `alktty`/`alkgit` type references live inside
`#[cfg(feature = "session")]` / `#[cfg(feature = "git")]` blocks —
imports, fields, accessors, Debug, constructor, tests), the "a
git-build never compiles alktty and vice versa" invariant is
compile-proven for both consumption sides.
### The unset-declines wire-level rows stay the mappings' consumption
The wave-3 mappings own the request-time decline checks; this task
lands the config-level contract rows (constructor posture, `Option`
configured() semantics, seam-vs-OQ-08-field independence, Debug
posture — 8 unit rows across the two feature modules) and leaves the
wave-2 harness rows untouched (featured-but-unset already declines at
the row the stubs own; the real serving paths make those rows
meaningful in `w3-git-mapping` / `w3-tty-session`, whose seam
consumption reads exactly these fields).
### Verification (all green)
`cargo test` (none build, 9 binaries + doctests) ·
`cargo test --features session` / `--features git` /
`--all-features` (67 unit rows under all-features) ·
`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` ✓.