- 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
167 lines
7.9 KiB
Markdown
167 lines
7.9 KiB
Markdown
---
|
||
status: draft
|
||
last_updated: 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](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](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](decisions/002-front-door-blind-core.md) | Session boundary | duplex + stateless entry points |
|
||
| [003](decisions/003-protocol-v2-first.md) | V2-first | honest advertisement; V0/V1 declined on fetch, push V0-framed (ADR-013) |
|
||
| [004](decisions/004-pack-pipeline.md) | Pack pipeline | generation on blocking threads, O(counts) |
|
||
| [005](decisions/005-session-substrate-types.md) | Substrate types | request reader, sideband sink, http framing rules |
|
||
| [009](decisions/009-bounded-resources-budget.md) | Budgets | `Limits` in every session tuple |
|
||
| [010](decisions/010-pure-protocol-crate.md) | Pure protocol crate | wire layer is backend-trait-only |
|
||
| [013](decisions/013-receive-pack-state-machine.md) | receive-pack | V0-framed push machine, thin-pack acceptance, unpack-first CAS |
|
||
| [014](decisions/014-v2-negotiation-ack-loop.md) | 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) |