Files
glm-5.3-flash 18106f461c docs(reviews): post-remediation re-review — gate passed, specs to reviewed
- docs/reviews/002-post-remediation-review.md: verifies all 14 review-001
  findings landed faithfully (sibling-source re-verification + gix-transport
  async_trait(?Send) check + four-config/MSRV probes), records the eight
  residual findings (R-1..R-8) and their resolutions (ADR-018 + doc batch)
- README lifecycle: draft→reviewed allows properly-tracked non-circular
  OQ deferrals (release-timing OQ-03 no longer blocks the transition) — R-7
- overview/transport/backend/doors/open-questions: frontmatter flipped to
  reviewed, timestamps refreshed; ADR-018 added to all ADR tables and the
  OQ-03 freeze-inventory narrative

Phase-1 gate verdict: decomposition may begin
Verification: cargo doc/test/clippy/fmt clean; four feature configs +
MSRV 1.88 check/clippy clean
2026-09-30 05:30:16 +00:00

209 lines
11 KiB
Markdown
Raw Permalink 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: reviewed
last_updated: 2026-09-30
---
# 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](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](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](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 |
| [016](decisions/016-native-session-preamble.md) | Native session preamble | `{repo, service}` open-op params, request-line preamble, service in the tuple |
| [017](decisions/017-consumer-half-git-session.md) | Consumer half | `GitSession` typed client (`ls_refs`/`fetch`/`push`), custom alkcall Transport + gix-protocol, hand-rolled push |
| [018](decisions/018-backend-trait-signatures-and-storage-error-model.md) | Trait signatures + storage errors | the seam shapes transport calls (`generate`/`prepare`/`apply_updates` signatures, `StorageError`, boxed-stream ownership) |
## 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)