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

167 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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)