Files
alkgit/docs/architecture/transport.md
T
glm-5.3-flash 11ceead6fd docs(architecture): N-4 + N-5 — push-options trait param, unborn-HEAD rider
- N-4: GitPackIngest's prepare binding carries push_options:
  Option<&PushOptions> (parsed (key, value) pairs, verbatim and
  un-interpreted; None until the config gate opens) — pinned in
  ADR-013 §11 and backend.md's trait description so opening the
  config gate later is value-additive, not a trait redesign
- N-5: ls-refs=unborn verification recorded as a rider in
  transport.md §ls-refs + tracker task tasks/architecture/
  n5-unborn-head-rider.md (unborn fixture, real client, both
  substrates; drop the token if it cannot be served — ADR-003)
- review 001: N-4, N-5 marked resolved

verification: cargo test, clippy -D warnings, fmt --check, doc — clean
2026-09-30 04:19:37 +00:00

11 KiB
Raw Blame History

status, last_updated
status last_updated
draft 2026-09-29

alkgit: Git Smart Protocol (wire layer)

What it is

The wire half of the alkgit protocol crate: pkt-line session substrates, protocol V2 state machines (advertisement, ls-refs, fetch, receive-pack), and the substrate types that keep doors and handlers from touching futures_io or raw packetline APIs. Depends on alkcall and — only under the gix feature — the backend implementations (ADR-010). The wire layer itself is backend-trait-only, which is what makes default-features = false compile without gix.

Substrate layer (ADR-005)

Two session entry points over one state-machine core. ADR-005 owns the full decision (what each substrate owns and why); the surface is:

  • Duplex session (the alk/git ALPN producer, channels-opened git sessions, embedder stream doors) — input: (peer identity, resolved repo id, service, authorized-repo marker, duplex stream, Limits). The marker is a type the door/adapter constructs only after the ADR-007 check passes (skipping the check is a type error — ADR-007; D-3). The service is selected at establishment on every native path (ADR-016: the channels open-op params {repo, service}, or the in-band git-daemon request line the direct-ALPN GitAdapter parses, POC-1-verbatim), and selects the state machine: the V2 capability advertisement for fetch, the V0 ref advertisement for push — both are server-emitted firsts, so the service must precede the stream. Encapsulates the split/compat/packetline bridge, the request reader (delim-aware parsing, reset() discipline, break-on-error), and the sideband writer.
  • Stateless session (smart-http doors, e.g. alkhttp's future git feature) — input: (peer identity, resolved repo id, service, authorized-repo marker, request-reader, response-writer, Limits) per http POST. The door's route still selects the service (doors.md), but the substrate input carries it explicitly — nothing in a POST body distinguishes an upload-pack POST from a receive-pack POST before parsing (ADR-016). Adds the http-framing rules (capability-dump skip, flush-only responses, probe handling). IO-abstract: the door supplies reader/writer; see doors.md for the mounting.

Both substrates feed the same V2 state machines; statelessness is a substrate property (per-request state), not a protocol fork.

Protocol core (ADR-003: V2-first)

Advertisement

  • Emitted once per duplex session (never between commands — real git hangs on re-advertisement); per-request-set on http (stateless: the client re-sends the dump).
  • Honest capability list: exactly what we serve (ls-refs=unborn, fetch=wait-for-done — the ack loop needs no capability change, the acknowledgments section is grammar not capability (ADR-014); object-format=sha1). Unimplemented features are declined by omission (validated against real git, POC-1). git-upload-archive is not served (fixed refusal — ADR-008's never-execute rule, ssh analog in doors.md).

ls-refs

  • Parse command=ls-refs (peel, symrefs, ref-prefix), stream ref lines from the backend's listing, flush. ref-prefix filtering is client-driven.
  • Unborn-HEAD rider (review 001 N-5): ls-refs=unborn is advertised (POC-1 validated the capability token was accepted, but serving an unborn HEAD's symref line was never exercised against real git — the only advertised promise without capture evidence). Implementation-phase verification: an ls-refs round against an unborn repo, real client, both substrates (the unborn repo fixture in the POC set has refs — a new one without any). If it cannot be served correctly, the honest move per ADR-003 is dropping the token.

fetch

  • Parse wants/haves/done/args; object-format check (reject mismatches — the object-format line is validated against the advertisement's object-format on every command; ADR-013 pins the push-side check to the same rule).
  • Negotiation policy: the full ack loop (ADR-014) — no-done rounds get an acknowledgments section (ACK <oid> per recognized have via the backend's common_haves, NAK when none, flush; never ready), the done round generates closure(wants) − closure(haves) via GitPackGen, with the boundary set the recognized subset (request haves filtered through common_haves — the same honest-boundary rule as the ack rounds; never honor an unverified have; ADR-014 §2). No cross-round state on either substrate (the client re-sends wants + commons each round — negotiation-captures.md). Advertisement text is unchanged: fetch=wait-for-done.
  • Pack generation via GitPackGen (ADR-004), streamed over sideband on duplex / sideband-in-response on http; the call is async-trait, with the pipeline-concurrency permit acquired around the call by the wire layer (ADR-009's enforcement point — backend.md concurrency model; the gix impl's internal spawn_blocking is its own detail).
  • Round/haves budgets enforced here (ADR-009); an empty resulting pack (client already has everything) is a valid zero-object packfile.

receive-pack (push)

V0-framed by upstream design (no version negotiation on the push path — ADR-013); shapes are capture-grounded (push-captures.md), not grammar-inferred.

  • Advertisement: V0-shaped ref advertisement — caps NUL-attached on the first ref line, capabilities^{} sentinel only for empty repos; served set report-status report-status-v2 delete-refs side-band-64k atomic ofs-delta object-format=sha1 (+ push-options under config gate); ACL before the first ref line (ADR-007).
  • Request: command lines (<old> <new> <ref>), shallow lines rejected up front for v1 (symmetric with fetch's decline, ADR-013 §4), flush, optional push-options section, then the pack stream — which is always expected (missing pack errors at unpack). An immediate flush is a client-side nothing-to-do (reply flush, no report).
  • Ingestion via GitPackIngest (ADR-004): thin packs accepted with bases from the server odb (default client behavior, no capability); Bundle::write_to_directory_eagerly with the repo's pack dir, .keep guard; gix-fsck connectivity per new tip; missing objects → unpack ng.
  • CAS timing: unpack-first, then per-ref checks (name via gix_validate::reference::name + reserved deny-list, CAS via gix-ref transactions, policy), atomic rollback, report — the observed upstream order (ADR-013 §7). One transaction per push.
  • Status report: band-1 pkt-line-framed (unpack ok|ng, per-ref ok|ng <ref> <reason>, inner flush, outer flush) when sideband was selected; bare pkt-lines otherwise. Substrate owns the framing (unwrapped reports abort real clients).
  • Http framing: Content-Length or chunked request (probe POST answered 200-empty above the client's postBuffer), Content-Type application/x-git-receive-pack-request / ...-result, response ends at flush (ADR-005 unchanged).
  • Push-options parsing and atomic rollback semantics per ADR-013 §10–11.

Error taxonomy

  • io errors are terminal (session ends); protocol errors map to pkt-line error bands (duplex) or http status + body (stateless). Substrate-level thiserror enum; no panics in library code (convention 2).
  • At the wire-mapping point (review 001 N-2), RegistryError::NotFound and an authorization failure collapse to the same wire error — the unknown-repo ≡ unauthorized indistinguishability rule (ADR-007/ADR-008) holds at the variant→wire mapping, so no variant leaks an existence oracle to the client.

Limits

Every session carries Limits (ADR-009): negotiation rounds, haves per round, receive-pack max size, wall clock, sideband chunk size (fixed 65000), advertisement ref cap. Missing Limits is a type-level error. The advertisement ref cap is fail-closed like every other budget (review 001 N-1): breach is a session error, never a silent truncation — a partial ref list is the worst failure mode an advertisement can have (clones appear to succeed).

Public API surface (v1)

Crate-root re-exports (alktty pattern; the full list in backend.md §public API): GitAdapter + register_openable, GitSession (the consumer half — ADR-017's typed client: ls_refs/fetch/push over the ADR-016 preamble; fetch via gix-protocol's client machinery over a custom alkcall Transport impl, push hand-rolled to ADR-013's shapes), substrate types (including the service selector and the authorized-repo marker), Limits, the backend traits (ADR-010's seam), protocol error enums; gix-feature types under the feature. Publish-freeze point: OQ-03.

Design Decisions

ADR Decision Summary
002 Session boundary duplex + stateless entry points
003 V2-first honest advertisement; V0/V1 declined on fetch, push V0-framed (ADR-013)
004 Pack pipeline generation on blocking threads, O(counts)
005 Substrate types request reader, sideband sink, http framing rules
009 Budgets Limits in every session tuple
010 Pure protocol crate wire layer is backend-trait-only
013 receive-pack V0-framed push machine, thin-pack acceptance, unpack-first CAS
014 Negotiation ack loop, no ready, wait-for-done stays
016 Native session preamble {repo, service} open-op params, request-line preamble, service in the tuple
017 Consumer half GitSession typed client (ls_refs/fetch/push), custom alkcall Transport + gix-protocol, hand-rolled push

Open Questions

  • OQ-03: publish/API freeze (partially resolved — single-crate shape settled by ADR-010; the native-path preamble shapes entered the freeze inventory via ADR-016).
  • OQ-05: sha256 policy (deferred(scope)).
  • OQ-02 resolved (ADR-014 — ack loop design).
  • OQ-04 resolved (ADR-013 — receive-pack state machine).

References

  • docs/research/poc-1-findings.md, docs/research/poc2-findings.md, docs/research/poc3-findings.md (the normative wire behavior — observed against real git, not docs' grammar)
  • docs/research/push-captures.md (the push-path normative record — ADR-013's basis; the git:// request-line framing ADR-016 adopts)
  • docs/research/negotiation-captures.md (the negotiation normative record — ADR-014's basis)
  • docs/research/git-protocol.md (inventory + observed corrections)
  • docs/research/gitoxide.md §"Wire format" (packetline contracts)
  • alktty wire.rs/session.rs/adapter.rs (the template's half shapes)