Files
alkgit/docs/architecture/transport.md
T
glm-5.3-flash e76f91f6d7 docs(architecture): resolve OQ-04 — receive-pack state machine (ADR-013)
- ADR-013: V0-framed push machine grounded in real git 2.43.0 captures
  (file://, git://, smart-http mock, raw stdio into real receive-pack):
  V0-shaped ref advertisement (caps on first ref line, capabilities^{}
  sentinel only for empty repos), served capability set, shallow requests
  rejected for v1, thin packs accepted with server-odb bases (no
  capability involved; push.thin default), ingestion bound to
  Bundle::write_to_directory_eagerly + gix-fsck + one gix-ref transaction
  per push (.keep-guarded), unpack-first CAS timing with observed
  upstream order, band-1 pkt-line-framed status report, http framing
  (probe/Content-Length/chunked), v1 update policy (CAS only; deletes
  and force-push allowed)
- docs/research/push-captures.md: the normative push wire record
- transport.md/backend.md/doors.md: receive-pack sections rewritten to
  the decided shapes; backend.md ingestion composition bound; stale
  OQ-04 references resolved
- ADR-003 amended: V2-only governs fetch; push is V0-framed by upstream
  design (fixes the V2-only contradiction found in review)
- ADR-009 amended: haves default reconciled with the client's stateless
  ceiling (16384); blocking-pipeline budget covers generation+ingestion
- OQ-04 resolved; tracker task closed; CAS-fail-fast optimization
  tracked (tasks/architecture/oq-13-cas-failfast.md)
- research index: poc findings + capture docs listed

Verification: cargo test / clippy -D warnings / fmt --check / doc pass
2026-09-25 04:05:07 +00:00

7.9 KiB
Raw Blame History

status, last_updated
status last_updated
draft 2026-09-25

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, duplex stream, Limits). 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, request-reader, response-writer, Limits) per http POST; 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.

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. 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; generation runs on spawn_blocking with the owned handle moved in (POC-2's shape: store shared, handle per session, generation on blocking threads).
  • 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).

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.

Public API surface (v1)

Crate-root re-exports (alktty pattern; the full list in backend.md §public API): GitAdapter + register_openable, GitSession, substrate types, 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

Open Questions

  • OQ-03: publish/API freeze (partially resolved — single-crate shape settled by ADR-010).
  • 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)
  • 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)