- 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
181 lines
10 KiB
Markdown
181 lines
10 KiB
Markdown
# 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:
|
|
|
|
1. **Self-hosted git platform** (the gitea-like app): stock git clients
|
|
over ssh/http doors; the consumer half is not on this path.
|
|
2. **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-protocol` models the **fetch/ls-refs client completely**:
|
|
`Command::{LsRefs, Fetch}`, `handshake()`, the full negotiation state
|
|
machine (`gix-negotiate`), and response parsing — driven by any
|
|
`gix_transport::client::Transport` impl.
|
|
- `gix-transport`'s client side is **transport-agnostic by explicit
|
|
design**: `client::Transport` is 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.**
|
|
|
|
1. **Operations, not raw access.** `GitSession` exposes:
|
|
- `ls_refs` — list remote refs (V2 `ls-refs` command, peel/symrefs/
|
|
ref-prefix arguments).
|
|
- `fetch` — wants + optional haves/negotiation round(s) → pack.
|
|
Full ack-loop negotiation (no-`done` rounds per ADR-014's grammar —
|
|
the client side of the loop we serve); `done` whenever 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-ref
|
|
`ok|ng`, `unpack ok|ng`), `atomic` and `push-options` as 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).
|
|
|
|
2. **A `gix_transport::client::Transport` impl 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) and `connect_direct` (the transport's handshake
|
|
writes ADR-016's git-daemon request line onto the stream, then yields
|
|
the response to `gix-protocol::handshake`). gix-protocol is activated
|
|
with its `async-client` feature (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.
|
|
|
|
3. **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 the `gix` feature, pack generation is the same
|
|
`GitPackGen`-class primitive the server half uses. Recorded as a
|
|
candidate for upstream `gix-protocol` contribution later; not a
|
|
dependency of this crate's design on it.
|
|
|
|
4. **Storage-agnostic: packs stream both ways; `GitSession` never touches
|
|
an object store.** `fetch` delivers the sideband-unwrapped pack bytes
|
|
to a caller-supplied consumer (the replicator/applies it to its own
|
|
odb — with the `gix` feature, `Bundle::write_to_directory`-class
|
|
ingestion is available, but alkgit does not couple to it).
|
|
`push` takes a caller-supplied pack stream + the caller-computed
|
|
commands. Client-side want/have computation, ref bookkeeping, and
|
|
local-merge policy are the application's; `GitSession` is 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).
|
|
|
|
5. **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.
|
|
|
|
6. **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`/
|
|
`push` against 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-client` feature 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_channels`
|
|
send), 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 deferred
|
|
`gix-protocol` call this resolves; the feature-split story unchanged)
|
|
- gix-protocol 0.65 (`Command::{LsRefs,Fetch}`, `handshake`, `fetch`,
|
|
`Arguments`, response parsing — `async-client` feature), gix-transport
|
|
0.59 (`client::Transport` trait + `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 |