- new ADR-017: GitSession is a real typed client in v1 (ls_refs/fetch/ push), grounded in the two deployment use cases — the p2p replicator is the named downstream and needs the client protocol layer; the thin-wrapper reading is superseded - fetch client reuses gix-protocol (async-client) over a custom alkcall gix_transport::client::Transport impl (handshake writes the ADR-016 request line on the direct path; the channels open-op params carry it otherwise); push hand-rolled to ADR-013's shapes (gitoxide has no send-pack client) - storage-agnostic: packs stream both ways to caller-owned consumers; no in-session credentials (alkcall transport authenticates); client sessions carry ADR-009 Limits (client is also internet-facing) - manifest: gix-protocol/gix-transport gain async-client features (verified against published tree, MSRV 1.88) - amend ADR-010 (consumer-half bullet) and ADR-012 §4 (the deferred gix-protocol call — resolved; rider superseded); backend/transport/ overview/doors wording; review 001 A-5 marked resolved verification: cargo check (default, --all-features, --no-default-features, --features sha256), cargo +1.88 check, cargo test, clippy -D warnings, fmt --check — clean across the matrix
10 KiB
ADR-017: The consumer half — GitSession is a real typed client in v1
Status
Accepted (resolves review 001 A-5; amends ADR-010's consumer-half sentence
and resolves ADR-012 §4's deferred gix-protocol call)
Context
ADR-010 named the consumer half — "GitSession typed client with
connect_direct and open_via_channels constructors … the alkcall-native
primitive for replication/mirroring in the alknet rewrite" — but no
document specified what it does, and review 001 (A-5, major) found the
largest uncut ambiguity for decomposition: either the half is silently
omitted (the headline claim unimplemented) or its scope is invented.
The review's initial reading ("thin wrapper like TtySession") does not
survive the use cases. The consumer half's downstream is the alk system's
two deployment targets:
- Self-hosted git platform (the gitea-like app): stock git clients over ssh/http doors; the consumer half is not on this path.
- The p2p github-like platform: replicators push and pull over direct alkcall connections. A replicator is a full git client — pull is wants/haves negotiation + pack application; push is V0 advertisement parsing + command/pack send + report parsing. There is no "no downstream yet" hedge here: the replicator is the named downstream, and the client protocol layer is the lower-level machinery it requires. Delegating pkt-line framing and machine state to every replicator workstream would repeat the exact mistake ADR-005 exists to prevent on the server side ("if adapters hand-roll the framing, each gets it subtly wrong" — the same principle, client side).
The manifest already carries gix-protocol and gix-transport
unconditionally (ADR-012 §4 had deferred "use gix-protocol's response
parsing or hand-roll the small client surface" to implementation time),
so the scope question also decided what those deps were for. Source
verification against the reference clone (0.87.1-equivalent) settled the
build-vs-reuse split:
gix-protocolmodels the fetch/ls-refs client completely:Command::{LsRefs, Fetch},handshake(), the full negotiation state machine (gix-negotiate), and response parsing — driven by anygix_transport::client::Transportimpl.gix-transport's client side is transport-agnostic by explicit design:client::Transportis a trait (handshake+request), built so a custom transport can plug in. A transport over an alkcall duplex stream is exactly its intended use.- Nothing in gitoxide models the send-pack (push) client at all — a push client is hand-rolled regardless of this decision.
The client grammar is capture-grounded without new capture work: the
push client's target grammar is pinned by push-captures.md (real git
pushes, ADR-013's normative basis) and the fetch client's by
negotiation-captures.md (ADR-014's) — the client machines speak the
same grammar the captures record and ADR-013/014 serve.
Decision
GitSession is a real typed git client in v1: both constructors, the
ADR-016 preamble, and ls_refs/fetch/push operations — fetch
reusing gitoxide's client machinery over a custom alkcall transport,
push hand-rolled to ADR-013's shapes, storage-agnostic throughout.
-
Operations, not raw access.
GitSessionexposes:ls_refs— list remote refs (V2ls-refscommand, peel/symrefs/ ref-prefix arguments).fetch— wants + optional haves/negotiation round(s) → pack. Full ack-loop negotiation (no-donerounds per ADR-014's grammar — the client side of the loop we serve);donewhenever the caller has the necessary refs locally (the common replicate-everything case needs no multi-round negotiation at all).push— ref updates + pack → server report (per-remote-refok|ng,unpack ok|ng),atomicandpush-optionsas caller- supplied options within the ADR-013 grammar. Each operation runs to completion (or error) on the session; the duplex session persists across operations (ALK duplex — no statelessness on the native path).
-
A
gix_transport::client::Transportimpl over the alkcall duplex stream drives the gitoxide client. One impl, two construction modes:open_via_channels(the preamble is the open-op's{repo, service}params — the transport's handshake writes nothing; alkcall carries it) andconnect_direct(the transport's handshake writes ADR-016's git-daemon request line onto the stream, then yields the response togix-protocol::handshake). gix-protocol is activated with itsasync-clientfeature (all transitively-pinned crates share the manifest's MSRV 1.88 — verified).gix-transport's async client traits are#[async_trait(?Send)]— a session drives one operation at a time on one task; the runtime bound is stated as the design shape, matching how a replicator uses a remote (an operation per remote at a time). The manifest's two unconditional deps become honest with this ADR: this is what they were for. -
Push is hand-rolled (gitoxide has no send-pack): a mirror of ADR-013's server shapes at the same honesty bar — parse the V0 ref advertisement (
capabilities^{}sentinel for empty repos), send command lines (old/new/ref with the correct zero-id create/delete shapes), optional push-options section, pack stream, then parse the status report in both framings (sideband band-1-wrapped and bare — the same distinction ADR-013 §8 pins server-side). Pack generation is not re-implemented: the caller supplies the pack (see 5) — for a replicator using thegixfeature, pack generation is the sameGitPackGen-class primitive the server half uses. Recorded as a candidate for upstreamgix-protocolcontribution later; not a dependency of this crate's design on it. -
Storage-agnostic: packs stream both ways;
GitSessionnever touches an object store.fetchdelivers the sideband-unwrapped pack bytes to a caller-supplied consumer (the replicator/applies it to its own odb — with thegixfeature,Bundle::write_to_directory-class ingestion is available, but alkgit does not couple to it).pushtakes a caller-supplied pack stream + the caller-computed commands. Client-side want/have computation, ref bookkeeping, and local-merge policy are the application's;GitSessionis the transport + grammar layer, not a git client policy engine (the app-level cases — remote-tracking refs, shallow policy UI, tag auto-follow — are the app's, on top). -
No credentials in the session. Identity is established by the alkcall transport before the preamble (the session tuple's identity input, ADR-016); the preamble carries no credential material; no client-side credential helper exists (the no-secret-material invariant; alkvault handles any replication credentials at the app layer). The gix-protocol handshake's credentials callback is never wired — the alkcall paths authenticate at the transport, not at git's credentials-protocol layer.
-
Bounded like every other session. Client operations carry
Limits(ADR-009): negotiation round budgets, wall-clock, max response pack size (a malicious or stalled peer server is the internet-facing threat; a hub must not stall a replicator unboundedly). Client-side breaches end the session with an error — the same fail-closed discipline as the server side.
Consequences
- Positive: the p2p replicator downstream has its primitive — a
replicator task is
open_via_channels/connect_direct+fetch/pushagainst its own storage, with no pkt-line exposure; ADR-010's producer/consumer claim becomes fully specified; the manifest deps carry purpose; client-vs-our-server integration tests are cheap (in-process, both halves in one test) — a verification surface neither the server-only design nor a real-git-only client would have; ADR-012 §4's deferred decision is resolved with the manifest unchanged. - Negative: v1's implementation surface grows — two client machines
(fetch via gitoxide integration, push hand-rolled) each need
integration tests against our server half and captures against real
git (for the push client's grammar fidelity to upstream behavior);
the
async-clientfeature of gix-protocol pulls its client-side tree (futures-lite, gix-negotiate, gix-credentials — MSRV-verified); the push client is a surface we own and maintain upstream-independent. - Neutral: the consumer half is wire-layer (no feature flag — it
compiles with
default-features = false); ADR-012 §4's "recorded, not an architecture commitment" rider is superseded by this ADR (an architecture commitment is exactly what was missing); ADR-010's consumer-half bullet is amended in place.
References
- Review 001 A-5 (the trigger; its option (c) direction, grounded here in the two deployment use cases rather than the review's (a) recommendation), OQ-03 (the freeze inventory this ADR's public API enters)
- ADR-010 (the producer/consumer structure — consumer half now
specified), ADR-016 (the preamble
connect_direct/open_via_channelssend), ADR-013 (the push grammar the hand-rolled client mirrors), ADR-014 (the negotiation grammar the fetch client speaks), ADR-005 (the no-hand-rolled-framing principle this ADR applies client-side), ADR-009 (bounded sessions, client-side), ADR-012 §4 (the deferredgix-protocolcall this resolves; the feature-split story unchanged) - gix-protocol 0.65 (
Command::{LsRefs,Fetch},handshake,fetch,Arguments, response parsing —async-clientfeature), gix-transport 0.59 (client::Transporttrait +Service/Protocol— the transport-agnostic client seam;#[async_trait(?Send)]mode) - alk-stack context: the two named downstreams (self-hosted platform app; p2p replicators with donation-based on-chain ACL/naming, off-chain git data) — vision.md is the record of the platform use cases
- doors.md §"The alkcall-native path", backend.md §public API, transport.md §public API, overview.md §crate map