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

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
review-wave-2
w1-config
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 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

  • ServeConfig gains 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::new shape is unchanged; feature-gated fields absent from featureless builds
  • Tests: the per-feature construction shapes, the unset-declines-preserved rows, the Debug posture
  • 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 ✓.