- 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
7.9 KiB
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/gitALPN 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
gitfeature) — 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-archiveis 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-prefixfiltering 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-formaton every command; ADR-013 pins the push-side check to the same rule). - Negotiation policy: the full ack loop (ADR-014) — no-
donerounds get anacknowledgmentssection (ACK <oid>per recognized have via the backend'scommon_haves,NAKwhen none, flush; neverready), thedoneround generates closure(wants) − closure(haves) viaGitPackGen. 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 onspawn_blockingwith 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 setreport-status report-status-v2 delete-refs side-band-64k atomic ofs-delta object-format=sha1(+push-optionsunder 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_eagerlywith the repo's pack dir,.keepguard;gix-fsckconnectivity 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 viagix-reftransactions, 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-refok|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
atomicrollback 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
thiserrorenum; 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)