- 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
10 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | ||
|---|---|---|---|---|---|---|---|---|---|
| w3-seam-config | Wave 3 — serve-config seam handles (alktty/alkgit per-feature) | completed |
|
narrow | low | component | 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
sessionseam 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 actualdrive_session[_pre_negotiated]signature — decide the exact final shape (flatOptionfields vs anOption-struct per seam; record):- the alktty backends map (
Arc<HashMap<String, Arc<dyn TtyBackend>>>— assembled by the assembly; alkssh never touches a real backend, thelocalshapes stay alktty-side); - the backend selector — the
NegotiateRequest.backendstring 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 todrive_session_pre_negotiated, family ACL layer riding along).
- the alktty backends map (
- The
gitseam handle (feature-gated), unset → the git rows decline even with the feature on. Contents, per alkgit 0.1.0'sserve_duplex_resolvedsignature: the four backend traits (GitRegistry/GitRefs/GitPackGen/GitPackIngest), the caller-owned server-widepipeline: Arc<tokio::sync::Semaphore>, andpush_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 sharedalkcall 0.10req → 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-landedArc<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
ServeConfiggains the feature-gated seam handles (the final shape decided and recorded); unset → the owned rows decline, structurally carried, no construction-time validation- 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) - The none-build's
ServeConfig::newshape is unchanged; feature-gated fields absent from featureless builds - Tests: the per-feature construction shapes, the
unset-declines-preserved rows, the
Debugposture cargo test,cargo clippy --all-targets -- -D warnings,cargo clippy --all-features --all-targets -- -D warnings,cargo fmt --check,cargo doc --no-depspass
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: optionalalktty = "0.8.1"/alkgit = "0.1.0"(bothdefault-features = false), wiredsession = ["dep:alktty"],git = ["dep:alkgit"]. alktty'slocalfeature stays off — alkssh never touches a real backend; thelocalshapes stay alktty-side. Feature-graph comment updated (wave-3 seam-config is the deps' first consumer; alktunnels still lands withw3-tunnel-direct).src/config.rs(re-exported throughlib.rs): the two seam-handle types + the two cfg-gatedServeConfigfields + the cfg-gatedsession_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 ouralkcall = "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'sdrive_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'sserve_duplex_resolved(duplex.rs:1412) takes the four traits +push_options_negotiated: bool+pipeline: Arc<Semaphore>.TtyBackend/NegotiateRequest/TerminalParamsWireandGitRegistry/GitRefs/GitPackGen/GitPackIngestare 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 ✓.