alkgit 0.1.0

Git smart protocol for the alk family: pkt-line substrates, protocol V2 serving (upload-pack/receive-pack), producer/consumer halves on alkcall channels, and the backend seam traits — the shipped alkstore+alkblobs composition is the alkgit-substrate crate (ADR-010/025)
Documentation
# Changelog

All notable changes to this crate are documented here. The format is
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
this crate adheres to [Semantic Versioning](https://semver.org/).

## [0.1.0] - 2026-10-23

The founding entry — the protocol core's first release. `alkgit` is
the git smart protocol for the alk family as a **pure protocol crate**
(ADR-010, the alktty/alktunnels template): the producer and consumer
halves of the git smart protocol riding alkcall channels on the
`alk/git` ALPN, with the backend seam (five `#[async_trait]` traits)
every storage implementation implements. The crate carries no binary
and no front doors — the alkhttp `git`/`lfs` features and alkssh are
family infrastructure; a downstream app embeds this crate, brings its
own door, and supplies its backends. The shipped alkstore + alkblobs
composition is the separate `alkgit-substrate` crate; this crate is
substrate-free by construction (ADR-025's two-member workspace).

The posture is **honest advertisement** (ADR-003): the wire advertises
exactly what is served — `fetch=wait-for-done` with the full ack loop,
`object-format=sha1` pinned (the `sha256` feature gates a passthrough
per OQ-05), unimplemented surface declined fail-closed rather than
half-served, and every session bounded by budgets (ADR-009): wall
clock, request bodies, advertisement refs, negotiation rounds, haves
per round, pack sizes, the ingestion decompressed-size cap, the
packless peek window, and the blocking-pool concurrency.

### Added

**Wave 1 — the seam** (engine-blind, everything later waves compose
against):

- The backend-trait family (ADR-018 §4): `GitRegistry` /
  `GitRegistryStore` (the registry family) and `GitRefs` /
  `GitPackGen` / `GitPackIngest` (the object-storage family, all
  `Send + Sync + #[async_trait]`, dyn-compatible, keyed on the
  resolved `RepoRecord`).
- The wire-shape seam types (ADR-018 §5): `RefLine` (with the unborn
  `oid: None` symref form), `RefUpdate`, `RefOutcome`,
  `PreparedPush`, `PushOptions`.
- The registry record: `RepoRecord` + `Visibility` + `AccessAction`
  (the ADR-015 three-action grant shape, opaque grant keys,
  `deny_unknown_fields` serde) and the repo-id grammar
  (`validate_repo_id`, backend.md §repo-id grammar).
- The error model: `RegistryError` (five variants) and `StorageError`
  (three variants) — infrastructure errors are `Err`, protocol
  failures are values (ADR-018 §7).
- The budget model `Limits` (ADR-009) with every default pinned;
  `max_object_size` (the ingestion decompressed-size cap) added in
  wave 5 pre-freeze.
- The session-substrate machinery shapes (ADR-005): the duplex
  pkt-line layer (`Session`, `Sideband`, `RawTail`) and the
  stateless smart-http framing (`StatelessRequest`,
  `StatelessResponse`, `BodyReader`), plus the error taxonomy
  (`SubstrateError`, `Service`).
- The in-memory backend test doubles (the contract-reference
  convention — every protocol wave's conformance work runs its
  machines against them).

**Wave 2 — the wire serves fetch** (upload-pack, dependency-free): 

- The V2 advertisement (`advertise_v2` — exactly the honest
  capability list, ADR-003) and the ls-refs machine (`serve_ls_refs`
  / `parse_ls_refs_args` — peel/symrefs/ref-prefix arguments, the
  unborn-HEAD symref line; the wave-5 rider-1 capture corrected the
  wire form to the upstream literal `unborn` token — a zero-oid HEAD
  is a fetch want of the null oid for a real client).
- The negotiation ack loop (ADR-014): `serve_fetch_round` +
  `parse_fetch_args` — no-`done` rounds answered with the exact
  `acknowledgments` grammar (`ACK <oid>` per recognized have, `NAK`
  only when no ACKs, never `ready`), the done round streaming
  `closure(wants) − closure(recognized subset)` as the packfile
  section, the want-less round answered empty — all behind the
  `GitPackGen::common_haves` seam (exists + is-commit).
- The serving entries: `serve_duplex` (advertisement-once + command
  loop under the wall clock) and `serve_stateless_post` /
  `serve_stateless_advertisement` (one POST = one command
  invocation, response rules: one-shot flush, never `0002`), both
  taking the ADR-007 `AuthorizedRepo` marker by type.
- The authorization front: `authorize` (the ADR-011/015 policy
  function — public read allows anonymous, write is always
  authenticated + granted, unknown-repo ≡ unauthorized) and
  `validate_ref_name` (ADR-013 §9's deny-list).
- The native transport (ADR-002/016): `GitAdapter` (the direct-ALPN
  producer half), the git-daemon request-line grammar
  (`parse_request_line` / `read_request_line`), and the
  `channels/git/sub` channels open op (`register_openable`) —
  unknown ≡ unauthorized collapsed to one generic denial at the gate.

**Wave 3 — receive-pack** (ADR-013 as amended by ADR-020):

- The V0 push advertisement (`advertise_v0`): caps NUL-attached on
  the first ref line, the `capabilities^{}` sentinel for empty repos,
  private repos advertise nothing (ACL first).
- The push request machine (`parse_push_request` /
  `serve_receive_pack`): command grammar (create/delete zero-id
  shapes), shallow rejection up front, `push-options` gated off v1,
  `receive-pack`'s nothing-to-do shape.
- The bounded packless peek (ADR-020 §1): the `packless_peek` window
  with the four ordering rules pinned (push-options consumed before
  the peek; zero-bytes → packless CAS-only path; a partial pack
  header is a malformed-push error, never a silent downgrade).
- The pack stream seam: `bridge_pack_source` (the async→blocking
  relay, `PackSourceBridge` / `PackSourceFault`) — wire-side pack-size
  budget, the raw-tail escape on both substrates — and
  `GitPackIngest::prepare` (the prepare/apply split; the single CAS
  home in `GitRefs::apply_updates`; every failure framed as the
  `unpack ng` leg, never `Err`).
- The status report framing (ADR-013 §8): band-1-wrapped with its
  inner flush when `side-band-64k` was selected, bare otherwise —
  substrate-owned so handlers cannot get it wrong.

**Wave 4 — the consumer half + the op family**:

- `GitSession` (ADR-017 as amended by ADR-023): the typed git client
  over gix-transport's git-daemon `Connection` on the alkcall
  `BiStream` compat halves. `connect_direct` rides the ADR-016
  request-line preamble; `open_via_channels` takes the post-open
  stream. `ls_refs` + `fetch` speak the client side of the same V2
  grammar we serve (full ack-loop negotiation, rounds as full
  stateless commands, the done round's pack streamed to the
  caller's sink); `push` is the hand-rolled send-pack (mirror of
  ADR-013's server shapes — gitoxide has no send-pack), with the
  client riders: a packless push holds the write side open until the
  report (ADR-020 §3) and every push reads the full report before
  the session could drop (ADR-023 §5). Client-direction budgets
  fail closed typed.
- The `git/repo/*` call-op family (ADR-012 §3/§5, gate per
  ADR-015 §3): `create` / `delete` / `update` / `get` as
  `OperationSpec` + handler pairs over `Arc<dyn GitRegistryStore>`
  (`register_repo_ops`), the PATCH semantics (review 002 R-3), the
  error-code table with the unknown ≡ unauthorized collapse, create
  seeding the caller's `{read, write, manage}` grants and minting
  ownership.

**Wave 5 — validation & release readiness:**

- The session split (review 005 R-1): `session_fetch` / `session_push`
  as flat sibling modules — zero public-API change.
- The ingestion decompressed-size cap (review 004 R-2,
  pre-freeze): `Limits::max_object_size` — pack-declaration-
  independent expansion bounding with incremental inflate (no
  allocation sized by the declared size); a breach is pack-level
  validation (`unpack ng`), never a session error.
- OQ-19 rider 2, the positive-ack capture: multi-have no-`done`
  rounds ack each recognized have in request order and the done
  round's boundary set equals the recognized subset — validated on
  both serving substrates (disposition: validated, no retraction).
- OQ-19 rider 1, the unborn-HEAD real-client validation: `git
  ls-remote` and a real clone of an unborn repo complete over both
  substrate doors, the unborn HEAD symref line served per the
  upstream literal-`unborn` emission (disposition: validated; the
  machine-side flush-only session end landed with the capture — the
  client's trailing flush is the quiet end, not a fault).
- OQ-19 rider 3, the real-client grammar tokens (validated against
  git 2.43.0 over both substrate doors): `include-tag` accepted and
  honored by omission (un-gated in the real client's V2 send path;
  the pack is the want closure as generated); `session-id=` honestly
  retracted (the advertisement never declares the capability, so a
  conforming client never sends it; the fail-closed decline stays);
  shallow/filter/server-option declined as unimplemented surface.
- The `fuzz/` harness: libFuzzer targets over the crate's ingress
  surfaces (the pkt-line framings, the request-line grammar, the
  negotiation command ingress, the repo-id grammar, the open-op
  params) with the committed corpus gate joining the verification
  list.

### Notes

- The `sha256` feature is a passthrough (wire capability line +
  request-side validation) — not a storage-format offer; the pinned
  default is sha1 (OQ-05).

[0.1.0]: https://git.alk.dev/alkdev/alkgit/releases/tag/alkgit-v0.1.0