refactor(architecture): ADR-010 — pure protocol crate (alktty template)

Structural decision (OQ-09 resolved): alkgit follows the alktty/
alktunnels template — a single published protocol crate on alkcall
channels, no binary, no front doors.

- ADR-010 supersedes ADR-001 (crate decomposition) and ADR-006
  (http router factory); both marked Superseded
- Single crate at repo root: Cargo.toml with gix feature (default-on
  backend implementations; wire layer compiles without it —
  gix-hash always-on with sha1 per the compile-time-rejected
  invariant), crates/ workspace deleted, src/lib.rs stub in place
- doors.md replaces http.md/ssh.md/alkgitd.md: alkhttp git-feature
  sequencing (after first publish), alkssh requirement (fixed-grammar
  exec dispatch), native alk/git path, downstream assembly
- backend.md replaces storage.md: GitRegistry/GitRefs/GitPackGen/
  GitPackIngest traits (ingest validates, refs commits — single CAS
  home), gix feature encodes POC-2 prerequisites
- transport.md reframed for the single crate; backend traits replace
  hook traits in the public API
- OQ-09 resolved (all five sub-decisions in ADR-010), OQ-01 resolved
  (subsumed), OQ-03 narrowed to publish-freeze, OQ-08 narrowed to
  registry identity + vault placement, OQ-07 rescoped to the gix
  feature's registry impl
- vision.md v2: single-binary/monorepo framing corrected as
  init-agent artifact; POC checklist marked complete
- AGENTS.md + .opencode agent specs updated to the new shape

Verification: cargo build (default + no-default-features), cargo test
--all-features, clippy --all-features -D warnings, fmt --check all
pass. Third review round: zero critical, all warnings/suggestions
addressed (GitPackGen signature amended in ADR-004, stale anchors
fixed, ADR-006 body tense normalized, CAS split stated, vision
residuals cleaned).
This commit is contained in:
glm-5.3-flash committed 2026-09-21 10:54:03 +00:00
1 parent de922253a4
commit 86bf5a0cf0
37 files changed
+730 -947

No files matched your search

+1 -1
View File
@@ -329,7 +329,7 @@ A decision should be `deferred(scope)` when:
- The use case isn't concrete (e.g., "we don't know what the admin API
will need from the call protocol")
- The options depend on something that doesn't exist yet (e.g.,
"depends on the alkgit-http streaming adapter shape")
"depends on the alkhttp git-feature shape")
- The trade-off requires data that can only come from implementation
(e.g., "need performance benchmarks to choose between X and Y")
- The decision is genuinely not needed for the current scope (e.g., "the
+3 -2
View File
@@ -206,11 +206,12 @@ Your task: {{task}}
8. Notify: worktree({action: "notify", args: {message: "Task completed: {{task}}. <brief summary>", level: "info"}})
Key project constraints (@alkdev/alkgit):
- Rust workspace: crates/alkgit-core, -transport, -http, -ssh, alkgitd
- Pure protocol crate: single `alkgit` at repo root (ADR-010); no
workspace, no binary, no front-door crates
- Rust: use cargo build, cargo clippy, cargo fmt, cargo test
- No comments in code
- thiserror for library error types; no panics in library code
- Feature flags for optional surface (sha256, acme)
- Feature flags for optional surface (gix default-on, sha256)
- Async via tokio runtime
- gitoxide (gix) for git storage/wire primitives; never shell out to git
```
+38 -34
View File
@@ -50,14 +50,16 @@ failed one.
Git identity is preconfigured (`glm-5.3-flash <glm-5.3-flash@alk.dev>`).
Do not change `git config`, skip hooks, or use `git commit -i`.
## Project Conventions (Rust / git-server workspace)
## Project Conventions (Rust / protocol crate)
This is alkgit — a self-hosted git server: repository storage, the git
smart protocol served over HTTP and SSH interfaces, and the `alkgitd`
binary that assembles it on the alk stack (alkcall, alkhttp, alktls,
alkvault) and gitoxide (gix). The project is a cargo workspace of
sub-crates under `crates/`. The conventions below apply to all work in
`crates/*/src` and `tests/`. They mirror
This is alkgit — a pure protocol crate (ADR-010, the alktty/alktunnels
template): the git smart protocol as producer/consumer halves on alkcall
channels (the `alk/git` ALPN), backend traits with a feature-gated
gitoxide (gix) implementation, no binary, no front doors (doors — alkhttp
`git` feature, alkssh — are family infrastructure; see
`docs/architecture/doors.md`). Single crate at the repo root (`src/`,
`tests/`). The conventions below apply to all work in `src/` and
`tests/`. They mirror
`.opencode/agents/implementation-specialist.md` §Project Conventions and
are repeated here so they apply to every session, not just spawned
implementation agents.
@@ -105,7 +107,7 @@ implementation agents.
is always authenticated. This is a spec-level invariant.
7. **gitoxide for git primitives, no shelling out** — storage and pkt-line
come from the gix crates (pinned in the workspace manifest). The
come from the gix crates (pinned in the crate manifest). The
serving path never spawns the `git` binary (no GPL dependency, no
process-injection surface). The server half of the smart protocol is
ours; see `docs/research/git-protocol.md` for the inventory.
@@ -122,10 +124,11 @@ implementation agents.
exists), its shape must not change. Protocol capability advertisement
is honest: never advertise what we don't serve.
10. **Feature flags** — optional surface is feature-gated: `sha256` (hash
algorithm passthrough; `sha1` is the default and pinned in the
workspace manifest) and `acme` (alktls ACME wiring in the binary).
Base crates compile lean. Verify both `cargo test` (default) and
10. **Feature flags** — optional surface is feature-gated: `gix`
(default-on; the backend implementations) and `sha256` (hash
algorithm passthrough; `sha1` is the default and pinned in the crate
manifest). The wire layer compiles without gix
(`default-features = false`). Verify both `cargo test` (default) and
`cargo test --all-features` pass if features are added.
11. **Bounded resources** — every protocol session carries wall-clock,
@@ -144,25 +147,25 @@ implementation agents.
constants.
14. **Module structure** — one module per file under `src/`, re-exported
from `src/lib.rs`. Public API surface is `lib.rs` re-exports.
Workspace members under `crates/`: `alkgit-core` (storage),
`alkgit-transport` (smart protocol), `alkgit-http` (http front door),
`alkgit-ssh` (ssh front door), `alkgitd` (binary).
from `src/lib.rs` (the alktty crate-root pattern). Public API surface
is `lib.rs` re-exports. Single crate at the repo root (ADR-010); the
gix backend implementation lives under the default-on `gix` feature;
`default-features = false` gives the wire/protocol layer only.
15. **Composability boundary ("ALPN as a service")** — `alkgit-core` +
`alkgit-transport` are front-door-blind: they consume (identity, repo
id, duplex stream, limits) and depend on alkcall types only, never on
alkhttp/alkgit-ssh, and carry no http/channel-specific types below the
stream. The http and ssh crates are replaceable adapters; a downstream
app embeds core + transport and brings its own front doors. v1 is the
reduction to storage + ACL + protocol adapters.
15. **Composability boundary ("ALPN as a service")** — alkgit is
front-door-blind: it consumes (identity, repo id, duplex stream,
limits) and depends on alkcall types only, never on alkhttp/alkssh,
and carries no http/channel-specific types below the stream. Doors
(alkhttp `git` feature, alkssh) are family infrastructure; a
downstream app embeds alkgit and brings its own doors. v1 is the
reduction to wire layer + backend traits + one gix implementation.
## Verification Commands
Run these before committing. All must pass.
```bash
cargo test # full suite (workspace)
cargo test # full suite
cargo clippy --all-targets -- -D warnings
cargo fmt --check
cargo doc --no-deps # if docs changed
@@ -174,17 +177,18 @@ If feature flags are added, also run `cargo test --all-features` and
## Lifecycle Status
The project is in **SDD phase 0** (exploration) — see
`docs/sdd_process.md` and `docs/research/README.md`. There is no
architecture yet (`docs/architecture/` is empty; ADR numbering starts at
001 when phase 1 begins). POC code never merges to main — two modes:
branch mode (POC builds on repo code; git branch, findings merged into
research docs, branch dropped) or standalone mode (POC independent of
repo code; scratch project at `/workspace/<poc-name>`). POCs have relaxed
The project is in **SDD phase 1** (architecture committed, implementation
not yet begun). Phase 0 research is in `docs/research/`; the committed
architecture is `docs/architecture/` (ADR-010 is the structural
decision: pure protocol crate, `alk/git` ALPN, backend traits, no
binary/doors). POC code never merges to main — two modes: branch mode
(POC builds on repo code; git branch, findings merged into research
docs, branch dropped) or standalone mode (POC independent of repo code;
scratch project at `/workspace/<poc-name>`). POCs have relaxed
constraints (comments/unwrap acceptable) — they are exploration tools,
not production code. Until phase 1
produces ADRs, "the architecture says" has no referent — cite
`docs/research/` docs instead.
not production code. Open architecture questions live in
`docs/architecture/open-questions.md` (OQ-04 receive-pack, OQ-06
registry backing, OQ-08 identity model are the active ones).
## Architecture Context
+40 -28
View File
@@ -1,46 +1,58 @@
[workspace]
resolver = "2"
members = [
"crates/alkgit-core",
"crates/alkgit-transport",
"crates/alkgit-http",
"crates/alkgit-ssh",
"crates/alkgitd",
]
[workspace.package]
[package]
name = "alkgit"
version = "0.0.1"
edition = "2021"
rust-version = "1.88"
license = "MIT OR Apache-2.0"
repository = "https://git.alk.dev/alkdev/alkgit"
description = "Git smart protocol for the alk family: pkt-line substrates, protocol V2 serving (upload-pack/receive-pack), producer/consumer halves on alkcall channels, and backend traits with a feature-gated gitoxide implementation"
keywords = ["git", "protocol", "alkcall", "vcs", "network"]
categories = ["network-programming", "asynchronous"]
[workspace.dependencies]
alkgit-core = { path = "crates/alkgit-core", version = "0.0.1" }
alkgit-transport = { path = "crates/alkgit-transport", version = "0.0.1" }
alkgit-http = { path = "crates/alkgit-http", version = "0.0.1" }
alkgit-ssh = { path = "crates/alkgit-ssh", version = "0.0.1" }
[lib]
name = "alkgit"
path = "src/lib.rs"
[features]
default = ["gix"]
# gitoxide-backed backend implementations (registry, refs, pack gen/ingest).
# Disable for a wire/protocol-only embed with your own backends.
gix = [
"dep:gix",
"dep:gix-odb",
"dep:gix-pack",
"dep:gix-ref",
"dep:gix-object",
"dep:gix-fsck",
]
# sha256 passthrough (untested end-to-end; sha1 is pinned by default — OQ-05).
# gix-hash stays always-on with sha1: the wire layer needs it, and gix-hash
# fails to compile with neither hash feature selected (compile-time-rejected
# invariant, see docs/research/gitoxide.md).
sha256 = ["gix-hash/sha256"]
[dependencies]
alkcall = "0.8"
alkhttp = "0.5"
alktls = "0.1"
alkvault = "0.1"
gix = { version = "0.87", default-features = false, features = ["sha1"] }
gix = { version = "0.87", optional = true, default-features = false, features = ["sha1"] }
gix-odb = { version = "0.84", optional = true, default-features = false, features = ["sha1"] }
gix-pack = { version = "0.74", optional = true, features = ["sha1"] }
gix-ref = { version = "0.67", optional = true }
gix-object = { version = "0.64", optional = true, features = ["sha1"] }
gix-fsck = { version = "0.25", optional = true, features = ["sha1"] }
gix-hash = { version = "0.26", features = ["sha1"] }
gix-packetline = "0.22"
gix-protocol = "0.65"
gix-transport = "0.59"
gix-pack = { version = "0.74", features = ["sha1"] }
gix-odb = { version = "0.84", default-features = false, features = ["sha1"] }
gix-ref = "0.67"
gix-object = { version = "0.64", features = ["sha1"] }
gix-hash = { version = "0.26", features = ["sha1"] }
tokio = { version = "1", features = ["rt-multi-thread", "io-util", "net", "fs", "time", "sync", "macros"] }
tokio = { version = "1", default-features = false, features = ["rt", "sync", "io-util", "macros", "time"] }
tokio-util = { version = "0.7", features = ["compat"] }
futures = "0.3"
bytes = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"
tracing = "0.1"
parking_lot = "0.12"
[dev-dependencies]
tokio = { version = "1", features = ["full", "test-util", "macros"] }
[profile.release]
lto = "thin"
-22
View File
@@ -1,22 +0,0 @@
[package]
name = "alkgit-core"
description = "Core git plumbing for alkgit: repository storage, ref management, pack handling, and access rules — transport-agnostic"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
gix = { workspace = true }
gix-odb = { workspace = true }
gix-pack = { workspace = true }
gix-ref = { workspace = true }
gix-object = { workspace = true }
gix-hash = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
[features]
default = []
sha256 = ["gix/sha256"]
-1
View File
@@ -1 +0,0 @@
#![forbid(unsafe_code)]
-25
View File
@@ -1,25 +0,0 @@
[package]
name = "alkgit-http"
description = "HTTP interface for alkgit: git smart-http (info/refs, git-upload-pack, git-receive-pack) and the admin API over alkhttp"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
alkgit-core = { workspace = true }
alkgit-transport = { workspace = true }
alkhttp = { workspace = true }
alkcall = { workspace = true }
bytes = { workspace = true }
tokio = { workspace = true }
futures = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
[features]
default = []
sha256 = ["alkgit-transport/sha256"]
-1
View File
@@ -1 +0,0 @@
#![forbid(unsafe_code)]
-21
View File
@@ -1,21 +0,0 @@
[package]
name = "alkgit-ssh"
description = "SSH interface for alkgit: git-over-ssh command serving (git-upload-pack / git-receive-pack) on alkcall channels"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
alkgit-core = { workspace = true }
alkgit-transport = { workspace = true }
alkcall = { workspace = true }
tokio = { workspace = true }
futures = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
[features]
default = []
sha256 = ["alkgit-transport/sha256"]
-1
View File
@@ -1 +0,0 @@
#![forbid(unsafe_code)]
-25
View File
@@ -1,25 +0,0 @@
[package]
name = "alkgit-transport"
description = "Git smart-protocol transport for alkgit: packetline framing, capability advertisement, upload-pack and receive-pack serving over alkcall BiStreams"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
alkgit-core = { workspace = true }
alkcall = { workspace = true }
gix-packetline = { workspace = true }
gix-protocol = { workspace = true }
gix-transport = { workspace = true }
gix-hash = { workspace = true }
bytes = { workspace = true }
tokio = { workspace = true }
futures = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
[features]
default = []
sha256 = ["alkgit-core/sha256"]
-1
View File
@@ -1 +0,0 @@
#![forbid(unsafe_code)]
-31
View File
@@ -1,31 +0,0 @@
[package]
name = "alkgitd"
description = "The alkgit server binary: assembles storage, smart protocol, HTTP, SSH, and TLS into one self-hosted git server"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
[[bin]]
name = "alkgitd"
path = "src/main.rs"
[dependencies]
alkgit-core = { workspace = true }
alkgit-transport = { workspace = true }
alkgit-http = { workspace = true }
alkgit-ssh = { workspace = true }
alkhttp = { workspace = true }
alktls = { workspace = true }
alkvault = { workspace = true }
tokio = { workspace = true }
serde = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
thiserror = { workspace = true }
[features]
default = []
sha256 = ["alkgit-http/sha256"]
acme = ["alktls/acme"]
-3
View File
@@ -1,3 +0,0 @@
fn main() {
println!("alkgitd: skeleton — server assembly lands after architecture (SDD phase 1)");
}
+23 -28
View File
@@ -5,56 +5,51 @@ last_updated: 2026-09-21
# alkgit Architecture
Phase 1 (SDD) output for alkgit — the self-hosted, single-binary git server
built on the alk stack (alkcall, alkhttp, alktls, alkvault) and gitoxide.
Phase 0 research lives in [docs/research/](../research/README.md); every
design claim here traces to a POC finding or research doc, or is flagged as
an open question.
Phase 1 (SDD) output for alkgit — the git payload service of the alk
family: a pure protocol crate on alkcall channels (the `alk/git` ALPN),
following the alktty/alktunnels template (ADR-010). Phase 0 research lives
in [docs/research/](../research/README.md); every design claim here traces
to a POC finding or research doc, or is flagged as an open question.
## Current State
Phase 1 is starting. All architecture documents below are `draft`
(ADR-006 additionally carries a Proposed ADR status pending OQ-01).
POC-1/2/3 validated the git protocol half end-to-end against real git 2.43;
the remaining design work is shape work (adapter composability, metadata/
registry backing, admin surface, receive-pack).
Phase 1, architecture committed to the pure-protocol-crate shape (ADR-010;
OQ-09 resolved). All docs below are `draft` except the superseded ADRs.
POC-1/2/3 validated the git protocol half end-to-end against real git
2.43; the remaining design work is the receive-pack state machine (OQ-04)
and backend/identity decisions (OQ-06, OQ-08).
## Architecture Documents
| Doc | Area | Status |
|---|---|---|
| [overview.md](overview.md) | Cross-cutting: crate map, dependency rules, security invariants | draft |
| [storage.md](storage.md) | `alkgit-core`: registry, refs, odb, pack generate/ingest, ACL types | draft |
| [transport.md](transport.md) | `alkgit-transport`: pkt-line sessions, V2 state machine, upload/receive-pack | draft |
| [http.md](http.md) | `alkgit-http`: smart-http adapter over alkhttp | draft |
| [ssh.md](ssh.md) | `alkgit-ssh`: git-command dispatch (wire SSH terminated by russh in alkgitd) | draft |
| [alkgitd.md](alkgitd.md) | `alkgitd`: binary assembly, config, TLS/ACME, serving loops | draft |
| [overview.md](overview.md) | Cross-cutting: crate shape, halves, security invariants | draft |
| [transport.md](transport.md) | Wire layer: substrates, V2 state machines, upload/receive-pack | draft |
| [backend.md](backend.md) | Backend traits + feature-gated gix implementation | draft |
| [doors.md](doors.md) | Door mappings: alkhttp `git` feature, alkssh requirement, native path | draft |
| [open-questions.md](open-questions.md) | Centralized OQ tracker | — |
## ADRs
| ADR | Decision | Status |
|---|---|---|
| [001](decisions/001-crate-decomposition.md) | Workspace crate decomposition (5 crates) | Accepted |
| [002](decisions/002-front-door-blind-core.md) | Front-door-blind core: session boundary (identity, repo, stream, limits) | Accepted |
| [001](decisions/001-crate-decomposition.md) | Workspace crate decomposition (5 crates) | Superseded (ADR-010) |
| [002](decisions/002-front-door-blind-core.md) | Session boundary (identity, repo, stream, limits) | Accepted |
| [003](decisions/003-protocol-v2-first.md) | Protocol V2-first with honest capability advertisement | Accepted |
| [004](decisions/004-pack-pipeline.md) | Pack generation/ingestion pipeline (gitoxide `data::output`) | Accepted |
| [005](decisions/005-session-substrate-types.md) | Session substrate types (duplex + stateless APIs, request reader) | Accepted |
| [006](decisions/006-http-adapter-composition.md) | HTTP adapter composition (alkgit-owned router factory) | Proposed |
| [004](decisions/004-pack-pipeline.md) | Pack pipeline (`data::output` gen / `data::input` ingestion) | Accepted |
| [005](decisions/005-session-substrate-types.md) | Session substrate types (duplex + stateless APIs) | Accepted |
| [006](decisions/006-http-adapter-composition.md) | HTTP adapter composition (alkgit-owned router factory) | Superseded (ADR-010) |
| [007](decisions/007-acl-before-advertisement.md) | ACL runs before any advertisement/ref line | Accepted |
| [008](decisions/008-registry-resolved-repo-identity.md) | Wire repo names are registry IDs, never paths | Accepted |
| [009](decisions/009-bounded-resources-budget.md) | Bounded-resources budget model (limits on every session) | Accepted |
Note: ADR-001's crate table and ssh.md record the v1 ssh-door decision
(russh terminates wire SSH in alkgitd; OQ-03 covers embedder variants).
| [009](decisions/009-bounded-resources-budget.md) | Bounded-resources budget model | Accepted |
| [010](decisions/010-pure-protocol-crate.md) | Pure protocol crate (alktty/alktunnels template) | Accepted |
## Open Questions
All unresolved questions are tracked in [open-questions.md](open-questions.md)
with stable OQ-IDs, priorities, and cross-references. Highest-priority opens:
OQ-09 (slim-crate model: doors as family infrastructure — may supersede
ADR-006/001 shape), OQ-04 (receive-pack validation), OQ-06 (metadata store
backing), OQ-08 (identity sources per front door).
OQ-04 (receive-pack validation), OQ-06 (registry backing), OQ-08 (registry
identity space + vault placement).
## Document Lifecycle
-96
View File
@@ -1,96 +0,0 @@
---
status: draft
last_updated: 2026-09-21
---
# alkgitd: Server Binary
## What it is
The assembly point: config parsing, listener setup (TLS/ACME), vault
wiring, the admin surface, and the glue that turns config into running
adapters. The only crate that knows the whole graph (ADR-001). No business
logic lives here.
## Assembly shape
For each listener (http and/or ssh), the binary:
1. Builds the identity provider (per-door; **OQ-08** decides mechanics).
2. Builds the registry (core trait + chosen backing; **OQ-06** decides
backing) and the storage roots from config.
3. Builds the transport hooks + `Limits` from config (ADR-009 defaults +
overrides).
4. http door: constructs the alkgit-http router factory (ADR-006) and
merges via `HttpAdapter::with_extra_routes`; wraps in alktls per
config; `Arc<HttpAdapter>` per accept task (POC-3 usage note).
5. ssh door: russh listener terminates SSH; alkgit-ssh dispatches the
exec requests (ssh.md §client compatibility).
6. Vault (alkvault) wired for any credential material; metadata/config
hold vault references only (convention 4/5).
## Config schema (v1 shape)
Server-facing values (registry backing location, storage roots, per-door
listeners/ports, TLS/ACME, limits overrides, vault path). The schema's
final shape depends on **OQ-06** (backing) and **OQ-08** (identity
config); the skeleton above is the stable frame.
Feature flags (convention 10): `sha256` (passthrough), `acme` (alktls
wiring). Base binary compiles lean; both feature sets verified in CI
(AGENTS.md verification commands).
## Admin API (scope: OQ-07)
- alkcall ops with `Visibility::Internal`, served over an admin-only
listener — never on the git traffic surface (alk-stack.md §gitea
lesson; vision threat-model notes).
- Candidate v1 op set (repo create/delete, visibility, ACL grant/revoke)
and its exact shapes: **OQ-07** — a dedicated session once the identity
model (OQ-08) exists, since op shapes depend on what identities mean.
- A downstream app may replace this surface entirely (vision
composability): the ops are alkcall ops, not embedded web endpoints.
## TLS/ACME
- alktls composition: `Connection::from_bidi(TlsStream, alpn)` — the
POC-1/3-validated alkcall entry shape. The http door rides TLS with
ALPN `http/1.1`/`h2`. The ssh door is a plain TCP listener
(stock `git clone ssh://` clients do not do TLS) — russh terminates SSH
directly on it (ssh.md); alkcall `Connection::from_stream` wraps the
post-auth stream when the adapter needs alkcall types.
- `acme` feature: alktls ACME state machine wired to config for the http
listener (alk-stack.md lists this as "likely trivial"; risk note: if the
wiring surprises, that becomes a research task before alkgitd
implementation — not silently absorbed).
## Serving loops
- Per-connection accept → `ProtocolHandler::handle` (alkcall ADR-002)
→ adapter dispatch → transport session.
- Server-wide blocking-pool budget for pack generation (ADR-009):
accepted/rejected at assembly time.
- Graceful shutdown: listener stop + session drain bounded by the
session wall clock (ADR-009).
## Design Decisions
| ADR | Decision | Summary |
|---|---|---|
| [001](decisions/001-crate-decomposition.md) | Crate decomposition | alkgitd = assembly only |
| [006](decisions/006-http-adapter-composition.md) | Router factory | alkgitd consumes the factory like any embedder |
| [008](decisions/008-registry-resolved-repo-identity.md) | Repo identity | storage roots configured server-side here |
| [009](decisions/009-bounded-resources-budget.md) | Budgets | limits overrides live in config |
## Open Questions
- **OQ-06**: registry backing (deferred(scope)).
- **OQ-07**: admin op set (open — after OQ-08).
- **OQ-08**: identity providers per door (open).
## References
- `docs/research/alk-stack.md` (integration surface, version pins)
- `docs/research/poc3-findings.md` (HttpAdapter usage, extra-routes behavior)
- alkcall ADRs: 002 (ProtocolHandler), 010 (capability injection), 017
(privilege model)
+103
View File
@@ -0,0 +1,103 @@
---
status: draft
last_updated: 2026-09-21
---
# Backend traits: the storage seam
## What this is
The payload side of the protocol crate: the traits a service deployer
implements (or consumes via the feature-gated gix implementation) so the
git protocol can talk to storage. Per ADR-010 this mirrors alktty's
`TtyBackend` pattern — traits in-crate, real implementation behind a
feature. Unlike alktunnels (no backend trait), git needs the seam: pack
generation/ingestion is too heavy to hard-wire.
## The trait family (ADR-010 sub-decision 4)
Four traits, kept small and orthogonal — the protocol crate never sees
gix types:
1. **`GitRegistry`** — repo id → (storage root, visibility, ACL scope).
The authoritative mapping (ADR-008); resolution failure is
indistinguishable from authorization failure (ADR-007). Metadata holds
vault references, never secrets.
2. **`GitRefs`** — listing for advertisement (refs + peeled tags + symref
targets, the ls-refs response data) and ref transactions (CAS apply
for receive-pack, name validation per git ref rules + reserved-
namespace deny-list).
3. **`GitPackGen`** — (repo, wants, haves, limits) → streaming pack
(`io::Write` consumer). Negotiation-agnostic. Missing objects abort
with an error, never a broken pack (ADR-004).
4. **`GitPackIngest`** — client pack stream → indexed pack + fsck/
connectivity report. It *prepares* the validated ref updates; the
transaction itself is applied by `GitRefs` (single CAS home — ingest
validates, refs commits). Budgeted (ADR-009 max pack size);
blocking-thread friendly.
Minimal-vs-full was the open sub-question; resolved as **full family** —
the four traits are each one or two methods plus types, and collapsing
them (e.g. refs into the registry) would force one impl block per
downstream where independent seams are cheaper to satisfy. The `gix`
feature implements all four; a downstream with its own object store
implements 3–4 and reuses 1–2, or none of it.
## The gix feature (default on)
- `alkgit = { default-features = true }` — wire layer + gix backend;
`default-features = false` — wire/protocol layer only (an embedder
brings its own backend). Hash: `sha1` pinned (the compile-time-rejected
invariant from `docs/research/gitoxide.md`); `sha256` passthrough
feature (OQ-05 policy unchanged).
- Encodes the POC-2 prerequisites by construction: odb handle sharing
(`Arc<Store>` shared, per-session handles, `prevent_pack_unload()` +
`ignore_replacements = true`), generation on blocking threads,
O(counts) memory, missing-objects abort.
- Received-pack ingestion via `gix-pack::data::input` (`streaming-input`)
+ `gix-fsck` + `gix-ref` transactions (ADR-004). Validation against
real `git push` is OQ-04.
## Concurrency model
- `gix` structures: `parking_lot` short-held locks; per-session handles
moved into `spawn_blocking` tasks (POC-2's shape: store shared, handle
per session, generation on blocking threads).
- Poisoned locks: `unwrap_or_else(|e| e.into_inner())` (convention 2).
- The traits are `Send + Sync` object-safe; impls run under the
adapter's tokio context.
## Public API surface
Crate-root re-exports (the alktty pattern): backend traits + types,
`GitAdapter`/`register_openable` (producer), `GitSession` (consumer),
substrate types, `Limits`, protocol error enums; `gix`-feature types
(`GixBackend`-family) exported under the feature. The embedder-facing
freeze point remains OQ-03 (narrowed: it is now this crate's own publish,
not a multi-crate freeze).
## Design Decisions
| ADR | Decision | Summary |
|---|---|---|
| [004](decisions/004-pack-pipeline.md) | Pack pipeline | `data::output` gen / `data::input` ingestion |
| [007](decisions/007-acl-before-advertisement.md) | ACL first | registry returns rule inputs |
| [008](decisions/008-registry-resolved-repo-identity.md) | Repo identity | wire names are registry ids |
| [009](decisions/009-bounded-resources-budget.md) | Budgets | limits flow into gen/ingest |
| [010](decisions/010-pure-protocol-crate.md) | Pure protocol crate | traits in-crate, gix behind a feature |
## Open Questions
- **OQ-06**: registry backing store (deferred(scope) — the trait is what
matters; the gix feature can ship a config-file/classic-on-disk impl,
and richer backing is downstream's choice).
- **OQ-04**: pack ingestion validation (deferred(unclear)).
- **OQ-05**: sha256 policy (deferred(scope)).
## References
- `docs/research/gitoxide.md` (API contract notes — normative for the
gix impl)
- `docs/research/poc2-findings.md` (generation pipeline + prerequisites)
- alktty `backend.rs`/`local` module (the trait + feature template)
- ADR-010 (the structural decision)
@@ -1,7 +1,8 @@
# ADR-001: Workspace crate decomposition (5 crates)
## Status
Accepted
Superseded by ADR-010 (pure protocol crate — single `alkgit`, no workspace,
no front-door crates, no binary)
## Context
@@ -6,7 +6,7 @@ Accepted
## Context
The load-bearing composability rule ("ALPN as a service",
`docs/research/vision.md`): `alkgit-core` + `alkgit-transport` must never
`docs/research/vision.md`): alkgit (the single crate) must never
know which front door is talking. POC-1 proved the exact shape survives the
wire: `Connection::accept_bi() → BiStream → tokio::io::split →
tokio-util compat → gix-packetline` ran a full V2 fetch against real git.
@@ -42,7 +42,7 @@ tuple (peer identity, resolved repo, limits):
command per invocation, matching smart-http's stateless framing. The
same core state machines run under both entry points.
Storage-facing side: transport calls `alkgit-core` for advertisement data,
Storage-facing side: the wire layer calls the backend traits for advertisement data,
pack generation (want/have set in → streaming pack out), and pack
ingestion + ref CAS (receive-pack). Storage never sees pkt-lines.
@@ -70,4 +70,4 @@ follow-up 2).
- alkcall ADR-005 (`BiStream` type), ADR-009 (BiStream as handler leaf)
- ADR-005 (substrate types detail), ADR-007/008 (what adapters do before
calling transport)
- overview.md §"Interfaces"
- overview.md §"Crate map"
@@ -41,7 +41,7 @@ Decision drivers:
(shallow, filter, packfile-uris, object-info, server-option are
*declined by omission*; POC-1 confirmed real git accepts this).
On the push side, `git-upload-archive` is not served at all — ssh
exec requests for it get a fixed refusal (ssh.md; ADR-008's
exec requests for it get a fixed refusal (doors.md; ADR-008's
never-execute rule).
- HTTP requests protocol V2 only; ssh requests protocol V2 only (see
below).
@@ -79,4 +79,4 @@ happens then.
- `docs/research/git-protocol.md` §"Protocol surface inventory", §"Negotiation policy"
- `docs/research/poc-1-findings.md` (advertisement-once, capability declination), POC-3 (http V2 framing)
- ADR-005 (substrate types), ADR-007 (ACL before advertisement)
- transport.md, http.md, ssh.md
- transport.md, doors.md
@@ -63,18 +63,23 @@ ship as compressed bases. No upstream delta-encode API exists
## Consequences
(2026-09-21 amendment, ADR-010: the crate layout changed — the type
below is now the `GitPackGen` backend trait in the single crate,
gix-free by signature `(repo, wants, haves, limits)`; backend.md is
authoritative for the trait shape. The pipeline itself is unchanged.)
- Fresh clones of loose-ish repos ship uncompressed bases (fine for v1;
clients re-pack at rest); repos kept packed get pack-copy efficiency.
- Memory stays O(counts); streaming under back pressure is proven on the
http path (POC-3: flat RSS under a 650 KB/s reader).
- Determinism: `objects_unthreaded` gives deterministic order; threaded
count + `InOrderIter` is the scale path later.
- `alkgit-core` exposes this as a type taking (odb handle, wants, haves) →
streaming pack; negotiation-agnostic (POC-2 follow-up 1).
- The generation seam is negotiation-agnostic: boundary sets in → pack
out (POC-2 follow-up 1).
## References
- `docs/research/poc2-findings.md` (the whole basis), `docs/research/poc3-findings.md` (streaming proof)
- `docs/research/gitoxide.md` §"Storage" (generation-pipeline notes)
- `docs/research/git-protocol.md` §"Server-side pack generation"
- ADR-005 (how the sink reaches the wire), ADR-009 (memory/time budgets)
- transport.md §fetch, storage.md §packs
- transport.md §fetch, backend.md §"The trait family" (GitPackGen)
@@ -29,7 +29,7 @@ If adapters or handlers hand-roll any of this, each gets it subtly wrong.
## Decision
`alkgit-transport` owns the substrate layer; adapters and handlers see
`alkgit` owns the substrate layer; doors and handlers see
friendly types:
1. **`Session` (duplex)** — wraps (stream, limits) into the pkt-line
@@ -1,9 +1,8 @@
# ADR-006: HTTP adapter composition — router factory in alkgit-http
## Status
Proposed (recommendation recorded; final call pending user review — OQ-01,
and now also OQ-09, whose slim-crate model may supersede this ADR's
shape entirely)
Superseded by ADR-010 (pure protocol crate — the http mounting moves to an
alkhttp `git` feature; the stateless substrate stays in `alkgit`)
## Context
@@ -45,7 +44,12 @@ Evaluation of Option B:
published — that is an alkhttp-side decision that does not constrain
alkgit's shape now.
## Decision (proposed)
## Decision
(Historical: this ADR was written as a proposal with the router factory
recommended; OQ-09/OQ-01 later resolved in favor of the alkhttp feature
instead, and ADR-010 superseded this ADR. Text below preserved as
written.)
**Option A.** `alkgit-http` owns the smart-http adapter and exposes it as
an axum router factory; alkhttp stays git-agnostic and unchanged.
@@ -67,12 +71,10 @@ The factory's seam is the composability surface:
downstream apps are second users of the same seam — this is what makes
the adapter genuinely composable rather than binary-only.
If a concrete downstream later demonstrates that the two-dep + merge
ergonomics is a real friction point, the Option-B-style sugar can be added
*in alkhttp* without any change here (alkhttp would gain an optional
alkgit-http feature re-exporting the factory). Deciding that now is not
necessary and would couple the release cadences; recording the escape
hatch here is enough.
(Historical) A concrete downstream later confirmed the ergonomics
friction; the Option-B sugar — an alkhttp `git` feature — was chosen as
the outcome (OQ-01/OQ-09), and ADR-010 adopted it as the structural
decision, superseding this ADR.
## Consequences
@@ -81,13 +83,13 @@ hatch here is enough.
- The identity-extractor callback is the one place downstream auth
semantics enter; ACL itself stays in core (ADR-007) — adapters never
hand-roll authorization.
- This ADR stays Proposed until OQ-01 is discussed (user flagged the
alkhttp-feature alternative; the escape hatch above is the recorded
reconciliation path).
- (Historical) This ADR was Proposed pending OQ-01; OQ-01/OQ-09 resolved
in favor of the alkhttp-feature alternative recorded below the
escape-hatch note, and ADR-010 superseded this ADR outright.
## References
- `poc3-findings.md` §"alkhttp fit" (with_extra_routes surface), follow-up 1
- `docs/research/vision.md` §"ALPN as a service", §"Sub-crate shape"
- alkcall ADR-027 (precedent and its limits)
- ADR-001 (crate decomposition), ADR-002 (session boundary), ADR-007/008/009
- http.md, OQ-01, OQ-08, OQ-09 (may supersede this ADR)
- OQ-01 (resolved), OQ-08, OQ-09; superseded by ADR-010
@@ -16,7 +16,7 @@ name arrives in-band in the first request line, *before* any ref data — so
the resolve-then-authorize step sits structurally ahead of the first
emitted line. POC-3 noted the http extra-routes are registered permissive
by default (the gateway bearer layer does not cover them) — making the
http-side check an explicit alkgit-http responsibility, not an inherited
http-side check an explicit door responsibility (alkhttp `git` feature), not an inherited
one.
## Decision
@@ -55,10 +55,10 @@ Push is always authenticated on every repo, no exceptions (vision §
- The check is cheap (registry lookup + ACL check) and runs before any
expensive protocol work.
- http adapters must wire ACL explicitly for their routes (POC-3 showed
alkhttp extra routes default permissive) — http.md encodes this.
alkhttp extra routes default permissive) — doors.md encodes this.
## References
- `docs/research/vision.md` §"Guiding principles" 1–2, §"Immediate threat-model notes"
- `docs/research/poc-1-findings.md` follow-up 4; `docs/research/poc3-findings.md` §"does NOT settle" (auth)
- alkcall ADR-017 (privilege model)
- ADR-008 (repo identity), http.md §auth, ssh.md §auth
- ADR-008 (repo identity), doors.md (door auth mechanics)
@@ -19,7 +19,7 @@ Wire-supplied repo names are **IDs**: opaque registry keys resolved
server-side to configured storage roots.
- The registry is alkgit's authoritative (repo id → storage root +
visibility + ACL scope) mapping, owned by `alkgit-core`.
visibility + ACL scope) mapping, owned by the `GitRegistry` backend trait.
- Resolution failure and authorization failure are indistinguishable to
the caller (ADR-007 step 2's no-existence-oracle rule).
- Wire names are never joined, normalized, or canonicalized into paths.
@@ -43,4 +43,4 @@ server-side to configured storage roots.
- `docs/research/git-protocol.md` §"Security-relevant protocol notes"
- `docs/research/vision.md` §"Immediate threat-model notes"
- ADR-007 (the resolve-then-authorize order)
- storage.md §registry, OQ-06 (registry backing)
- backend.md §"The trait family" (GitRegistry), OQ-06 (registry backing)
@@ -23,10 +23,12 @@ are bugs. Each POC surfaced specific unbounded surfaces that need budgets:
## Decision
Every session carries a `Limits` value, constructed by the adapter from
server config and handed to transport as part of the session tuple
(ADR-002). Defaults are per-crate constants; overrides are server config
in `alkgitd`.
Every session carries a `Limits` value, constructed by the door/adapter
from assembler config and handed to the wire layer as part of the session
tuple (ADR-002). On the native path the producer adapter is in-crate
(`GitAdapter`), but `Limits` still originates from assembler config —
the adapter's constructor takes it. Defaults are crate constants;
overrides are assembler config.
| Budget | Applies to | Default direction |
|---|---|---|
@@ -61,4 +63,4 @@ gets the hard cap.
- `docs/research/vision.md` §"Guiding principles" 7
- `docs/research/poc2-findings.md` follow-ups 2–3; `docs/research/poc3-findings.md` follow-up 3
- alkcall ADR-040 (channel backpressure — the thing git sessions bypass)
- ADR-002 (session tuple), transport.md §limits, http.md §budgets
- ADR-002 (session tuple), transport.md §Limits
@@ -0,0 +1,107 @@
# ADR-010: Pure protocol crate — alkgit follows the alktty/alktunnels template
## Status
Accepted (supersedes ADR-001, ADR-006)
## Context
OQ-09 captured a structural correction to how alkgit was initially framed.
The monorepo + binary setup (`alkgit-core`/`alkgit-transport`/`alkgit-http`/
`alkgit-ssh`/`alkgitd`) came from the repo-initialization agent's rendering
of loosely-scoped use-case discussions — not a deliberate architecture
decision. It diverged from the established family pattern: the alk
protocol crates (alktty, alktunnels, alksocks in progress) are single
published crates implementing a service as a producer/consumer protocol on
top of alkcall channels, with doors (http, ssh, net) owned by separate
family crates that downstream consumers assemble.
The git service fits that pattern exactly:
- **Producer half** = POC-1's substrate verbatim: an adapter implementing
alkcall `ProtocolHandler` for the `alk/git` ALPN → `accept_bi()` →
duplex V2 session, plus a `register_openable` helper so a git session is
openable through the `alk/channels` multiplexer (alktty ADR-007/009
pattern: the channels open-op carries the negotiation — here, the repo
id as params, which is also the natural ACL enforcement point).
- **Consumer half** = a typed `GitSession` client (alktty's `TtySession`
analog) driving fetch/push against a remote alkgit service — the
alkcall-native primitive for replication/mirroring in the alknet
rewrite.
- **Backend seam** = traits for registry, ref listing, pack generation,
and pack ingestion; the gix implementation ships behind a feature flag
(alktty's `local`-feature analog). Unlike alktunnels (no backend trait,
their ADR-004), git needs the storage seam — pack generation/ingestion
is too heavy to hard-wire — and unlike alktty, wasm-cleanliness is NOT a
goal: it falls out of the wire layer being gix-free, but nothing targets
wasm.
- **Smart-http** = the stateless substrate (ADR-005) stays in the protocol
crate; the http *mounting* (routes, content types,
`HttpAdapter::with_extra_routes` wiring) becomes an alkhttp `git`
feature — published after alkgit (see doors.md). POC-3 proved that
mapping is thin (its route layer was a thin wrapper over the stateless
substrate).
- **git-over-ssh** = alkssh's job when that crate exists (after alksocks).
The temporary russh-in-alkgitd decision (ssh.md) is dropped, not
shipped. The requirement alkgit places on alkssh is small and recorded
in doors.md.
Why now: nothing is implemented (the crates/ tree is an empty skeleton),
so the entire migration is documentation and manifest surgery. POC work
maps 1:1 onto the new shape, so no validation is lost.
## Decision
**alkgit is a single protocol crate** following the alktty/alktunnels
template:
- One crate, one repo (no cargo workspace, no sub-crates). Published to
crates.io as `alkgit`.
- **Producer half**: `GitAdapter` (direct `alk/git` ALPN via
`ProtocolHandler`) + channels `register_openable` (open-op params carry
the repo id — the negotiation, and the ACL enforcement point).
- **Consumer half**: `GitSession` typed client with `connect_direct` and
`open_via_channels` constructors.
- **Backend traits** (registry, refs, pack-gen, pack-ingest) in-crate;
`gix` implementation behind the default-on `gix` feature (disable it to
embed your own storage).
- **Substrate layer in-crate**: duplex session (ADR-002 boundary) +
stateless request/response substrate (ADR-005) — the stateless side is
IO-abstract (request-reader/response-writer), so alkhttp's feature maps
routes onto it without alkgit knowing http exists.
- **No binary. No front doors.** Assembly is downstream's job (the
platform deployment, or a future tiny assembly crate once alkssh/alknet
exist to assemble against).
- ALPN: `alk/git` (matches `alk/tty`, `alk/tunnel`, `alk/socks5`).
- Session tuple, security invariants (ADR-007/008/009), V2-first protocol
(ADR-003), pack pipeline (ADR-004), substrate types (ADR-005) all carry
over unchanged — they are pattern-independent.
## Consequences
- Positive: the embedder seam is *stronger* than ADR-001's shape (backend
traits let a downstream use its own storage instead of dragging gix
in); release surface shrinks to one crate; git becomes the first
payload service proven across three doors (alkhttp, alkssh, alknet)
with one protocol core; auth semantics collapse to "the door's auth"
(OQ-08 narrows sharply); POC-validated shapes are preserved verbatim.
- Negative: `vision.md`'s "single-binary git server" framing is amended —
the binary was never the user's intent (OQ-09 context); git-over-http
now ships on alkhttp's release cadence (feature lands in alkhttp 0.6
after `alkgit` is published); git-over-ssh waits for alkssh (no ssh
path in the interim unless a downstream adds its own wire-ssh
termination via the duplex session — that is a legitimate embedder
path, not alkgit scope).
- Neutral: crate granularity sub-decision resolved as "merge" (the
storage-only embedder concern is served by feature-gating, not
splitting: `default-features = false` gives the wire/protocol layer
without gix).
- ADR-001 (crate decomposition) and ADR-006 (http router factory) are
superseded; ssh.md/http.md/alkgitd.md are replaced by doors.md.
## References
- OQ-09 (the discussion this resolves), OQ-01 (subsumed), OQ-03 (narrowed)
- alktty docs/architecture (template), alktunnels docs/architecture (the
no-binary, feature-gated `local` precedent)
- POC-1/2/3 findings (the 1:1 mapping evidence)
- ADR-002 (unchanged, load-bearing), ADR-003/004/005/007/008/009
(carry over), doors.md, backend.md, overview.md
+115
View File
@@ -0,0 +1,115 @@
---
status: draft
last_updated: 2026-09-21
---
# Doors: how alkgit is exposed
## What this is
alkgit (per ADR-010) owns no front doors. This document records the two
door mappings that exist or are planned in the family, and the requirements
alkgit places on each. The protocol crate itself is door-blind (ADR-002):
every door converges on the same two substrates.
## The door pattern
Doors are family infrastructure: alkhttp (exists), alkssh (planned, after
alksocks), the alknet rewrite (coming). A door wraps alkcall's
producer/consumer in its wire protocol; services like git, tty, tunnels,
and socks5 are payloads doors optionally expose. Downstream consumers
(our platform deployment, a future gitea-like app) assemble the doors they
want with the payloads they want.
Auth semantics are the door's auth (http: the door's token mechanism; ssh:
the door's key-based identity). alkgit consumes the resulting alkcall
identity — the identity-extractor seam that ADR-006 needed exists only in
the door, where it belongs.
## alkhttp `git` feature (http mounting)
**Scope**: an alkhttp feature that maps two route shapes onto alkgit's
stateless substrate (ADR-005):
| Route | Service |
|---|---|
| `GET /{repo}/info/refs?service=git-{upload,receive}-pack` | advertisement (smart prefix + capability dump + flush) |
| `POST /{repo}/git-upload-pack` | V2 fetch commands (one command per POST) |
| `POST /{repo}/git-receive-pack` | push (streaming ingestion, budgeted body) |
| `GET /{repo}/info/refs?service=git-upload-archive` | honest refusal (not served) |
`{repo}` is the registry id (ADR-008); resolution + ACL run before any
response byte (ADR-007).
**Sequencing**: the feature requires `alkgit` on crates.io (optional
dependencies must resolve), so it lands in alkhttp 0.6 after alkgit's
first publish. Until then, git-over-http is served by any downstream that
mounts the stateless substrate directly — POC-3's `httpservice.rs` is the
reference implementation of exactly that mapping.
**Framing facts the feature must honor** (all POC-3-validated, encoded in
the substrate, not re-decided): responses end at flush (never `0002`);
flush is one-shot per response; flush-only POSTs are probes answered
200-empty; request bodies stream (no accumulation); response bodies
stream under back pressure (bounded mpsc → `Body::from_stream`); request
bodies carry a budget (ADR-009 — alkhttp custom routes are unbounded by
default).
## alkssh (git-over-ssh, future)
**alkgit's requirement on alkssh** (to record in alkssh's spec when it
exists): parse the exec-request string with a fixed grammar —
`git-upload-pack '<repo>'` / `git-receive-pack '<repo>'` — never shell-
interpret it (ADR-008's never-execute rule), map the door's key-based
identity to the alkcall identity space, resolve the repo id against the
registry, run ACL, and hand (identity, repo, post-auth stream, limits)
to alkgit's duplex session. `git-upload-archive` gets a fixed refusal.
V2 is expected (ADR-003); `GIT_PROTOCOL=version=2` rides the ssh env
mechanism.
**Interim**: no git-over-ssh path ships with alkgit. A downstream that
needs it before alkssh lands can terminate wire-ssh itself (russh or
otherwise) and consume the duplex session — that is exactly the embedder
path ADR-002 defines, and it works today against the POC-1 shape. It is
an embedder assembly concern, not alkgit scope.
## The alkcall-native path (no adapter at all)
The `alk/git` ALPN producer and the channels open-op (repo id in the
open-op params) need zero door code — POC-1 is that shape verbatim. Any
alkcall-speaking client (including alkgit's own consumer half,
`GitSession`) can use it. This is the baseline path; the http/ssh doors
are conveniences layered on top for stock git clients.
## Assembly (downstream responsibility)
There is no alkgit binary (ADR-010). A deployment assembles: alkcall
connection sources (alkhttp/alktls for http, alkssh later, raw ALPN for
the native path) + `GitAdapter` + a backend implementation (the `gix`
feature's, or its own) + `Limits` from config + vault for any credential
material. Reference sequence for our platform deployment lives in that
deployment's docs, not here.
## Design Decisions
| ADR | Decision | Summary |
|---|---|---|
| [002](decisions/002-front-door-blind-core.md) | Session boundary | every door consumes the same tuple |
| [003](decisions/003-protocol-v2-first.md) | V2-first | honest advertisement per door |
| [007](decisions/007-acl-before-advertisement.md) | ACL first | before any protocol byte |
| [008](decisions/008-registry-resolved-repo-identity.md) | Repo identity | wire names are registry ids |
| [009](decisions/009-bounded-resources-budget.md) | Budgets | limits on every session |
| [010](decisions/010-pure-protocol-crate.md) | Pure protocol crate | doors are family infrastructure |
## Open Questions
- **OQ-08**: registry identity space + vault placement (narrowed by
ADR-010; door auth mechanics belong to the door crates).
## References
- `docs/research/poc-1-findings.md` (duplex shape), `docs/research/poc3-findings.md`
(http mounting evidence, framing facts)
- alktty/alktunnels architecture docs (the template this follows)
- ADR-010 (supersedes ADR-001/006; this file replaces http.md, ssh.md,
alkgitd.md)
-96
View File
@@ -1,96 +0,0 @@
---
status: draft
last_updated: 2026-09-21
---
# alkgit-http: HTTP Front Door
## What it is
The smart-http adapter: git's http protocol served over alkhttp. Owns the
smart-http endpoints, the http-framing composition (streaming both ways),
and the ACL wiring that alkhttp's extra-routes surface does not provide by
default. **Shape partially pending OQ-01/ADR-006** (router factory vs
alkhttp feature).
## Routes (POC-3-validated shapes)
| Route | Service | Notes |
|---|---|---|
| `GET /{repo}/info/refs?service=git-{upload,receive}-pack` | advertisement | smart prefix + capability dump + flush; `application/x-git-*-pack-advertisement`; no-cache |
| `GET /{repo}/info/refs?service=git-upload-archive` | — | **not served** — honest refusal (404/403 class), same rule as ssh (ADR-003) |
| `POST /{repo}/git-upload-pack` | V2 fetch commands | one command per POST |
| `POST /{repo}/git-receive-pack` | push | streaming ingestion, budgeted body (OQ-04) |
- `{repo}` is the registry ID (ADR-008) — path segment → registry lookup,
never a path.
- Reserved-path collision checks stay with alkhttp
(`HttpAdapter::with_extra_routes` verifies; POC-3 confirmed passing).
## Session flow (per request)
1. Extract repo ID from the path; resolve + authorize **before any
response bytes** (ADR-007/008). Unknown == unauthorized (no existence
oracle). Identity comes from the downstream auth model (**OQ-08**).
2. `GET info/refs`: emit advertisement (via transport stateless
substrate); ACL checked before the smart prefix line.
3. `POST git-upload-pack`: build `StatelessRequest` (ADR-005) — the
substrate handles capability-dump skipping, delim-aware parsing,
flush-only responses, probe handling.
4. `POST git-receive-pack`: streaming body → transport receive-pack;
body budgeted (ADR-009; alkhttp extra routes are unbounded by default
— the budget is *ours*, POC-3 follow-up 3).
## Streaming composition (POC-3-proven)
- Request bodies: axum `BodyDataStream` → chunk-at-a-time reader →
packetline parser; no accumulation (dribble-probe verified). The
`futures-io` reader adapter lives in transport (ADR-005).
- Responses: sideband/protocol output → bounded mpsc (8 slots) →
`Body::from_stream`; back pressure parks the blocking-side generator;
first chunk leaves in milliseconds; RSS flat under slow readers.
- One sideband chunk = one HTTP chunk (65000-byte chunks carry over).
## Composition seam (ADR-006 — proposed, OQ-01)
The crate exposes the adapter as a factory (shape per ADR-006):
dependencies in (identity extractor, registry, transport hooks, limits),
an axum `Router` out, ready for `HttpAdapter::with_extra_routes`.
`alkgitd` is the first consumer; a downstream alkhttp deployment is the
second. If adopted, this section becomes the seam spec.
`HttpAdapter` is not `Clone` (POC-3): assembly holds `Arc<HttpAdapter>`
per accept task — the intended usage.
## Auth
- The gateway bearer layer does not cover extra routes' handlers unless
registered before it (POC-3): ACL wiring here is explicit, not
inherited.
- Identity extraction mechanics (bearer/basic/…) are **OQ-08**; the
factory seam takes it as a callback so downstream apps plug theirs in.
- Anonymous fetch: per-repo opt-in; push always authenticated (ADR-007).
## Design Decisions
| ADR | Decision | Summary |
|---|---|---|
| [002](decisions/002-front-door-blind-core.md) | Session boundary | stateless substrate per POST |
| [005](decisions/005-session-substrate-types.md) | Substrate types | http framing rules live in transport |
| [006](decisions/006-http-adapter-composition.md) | Router factory | **proposed** — OQ-01 open |
| [007](decisions/007-acl-before-advertisement.md) | ACL first | explicit wiring, not inherited |
| [008](decisions/008-registry-resolved-repo-identity.md) | Repo identity | path segment = registry ID |
| [009](decisions/009-bounded-resources-budget.md) | Budgets | request-body cap on POSTs |
## Open Questions
- **OQ-01**: adapter home/composability (partially resolved — ADR-006
proposed).
- **OQ-08**: identity sources (open — blocks the auth section).
- **OQ-04**: receive-pack over http (deferred(unclear)).
## References
- `docs/research/poc3-findings.md` (route shapes, framing facts, streaming measurements
— normative)
- `docs/research/alk-stack.md` §"Interfaces in alkcall terms"
+80 -119
View File
@@ -23,46 +23,54 @@ when their impacts say so; it records how careful the resolution must be.
| OQ | Status | Blocked on / investigation |
|---|---|---|
| OQ-09 | open (discussion) | slim-crate model: doors as family infrastructure; subsumes OQ-01 if Slim-B |
| OQ-04 | deferred(unclear) | receive-pack walkthrough (capabilities, shallow, thin-pack, CAS timing) + push POC |
| OQ-06 | deferred(scope) | concrete metadata-scale requirements (feeders: OQ-07, OQ-08 outputs) |
| OQ-05 | deferred(scope) | ecosystem need for sha256 |
## Theme: composition / crate shapes
### OQ-09: Slim-crate model — doors as family infrastructure, git as a payload service
- **Origin**: user session (2026-09-21); ADR-006, ADR-001, ssh.md
- **Status**: **resolved** — ADR-010 (pure protocol crate, the alktty/
alktunnels template): single `alkgit` crate, producer/consumer halves,
backend traits with feature-gated gix, no doors, no binary; ALPN
`alk/git`; http mounting → alkhttp `git` feature; git-over-ssh →
alkssh (russh scaffolding dropped); the monorepo/binary framing was an
init-agent artifact (vision amended).
- **Resolution**: [decisions/010-pure-protocol-crate.md]. All five
sub-decisions recorded there (ssh deletion, http home, crate
granularity, backend-trait surface, binary fate).
- **Cross-references**: ADR-001/006 (superseded), OQ-01, OQ-03, OQ-08,
doors.md, backend.md, overview.md
### OQ-01: HTTP adapter home and composability (alkhttp `git` feature vs alkgit-owned factory)
- **Origin**: [overview.md], [http.md], user session question
- **Status**: partially resolved — ADR-006 written **Proposed** with
Option A (alkgit-owned router factory) as the recommendation and the
alkhttp-side sugar as a recorded escape hatch. Needs user review.
- **Door type**: two-way (adapter shape can change before anything is
published)
- **Priority**: high
- **Impacts**: blocks finalizing http.md and ADR-006; small effect on
downstream ergonomics.
- **Resolution path**: user reviews ADR-006; accept → ADR becomes
Accepted; or choose Option B (alkhttp feature) → ADR reworked.
- **Cross-references**: ADR-001, ADR-006, http.md
- **Origin**: user session question (OQ-01, resolved 2026-09-21)
- **Status**: **resolved** (subsumed by OQ-09/ADR-010) — the smart-http
stateless substrate stays in `alkgit` (IO-abstract); the http mounting
(routes, content types, `with_extra_routes` wiring) becomes an alkhttp
`git` feature published after alkgit's first publish. The ADR-006
router-factory shape is superseded; the alkhttp-side feature is the
outcome.
- **Cross-references**: ADR-006 (superseded), ADR-010, doors.md
### OQ-03: Downstream embedding surface (what "embeds core + transport" means concretely)
### OQ-03: Downstream embedding surface (what "embeds alkgit" means concretely)
- **Origin**: [overview.md], [transport.md], [ssh.md], vision §ALPN as a
service
- **Status**: partially resolved — v1 ssh-door decision made: alkgitd
terminates SSH via russh for stock git clients (ssh.md); what remains
deferred is whether a *pure-alkcall-channels* ssh variant and a
russh-flavored adapter are exported for embedders.
- **Door type**: two-way
- **Origin**: [overview.md], [transport.md], vision §"ALPN as a service"
- **Status**: **partially resolved** — the shape is settled (ADR-010):
embedding = one crate + backend traits (own storage via
`default-features = false`, or the gix feature) + optional door
features. What remains deferred is the publish-time API freeze itself:
which type/feature names are pinned at first crates.io publish.
- **Door type**: one-way (API freeze is registry-visible to dependents)
- **Priority**: medium
- **Impacts**: blocks nothing in v1 (alkgitd is the only consumer); shapes
the crates' public API freeze before publish.
- **Blocked on**: a concrete downstream embedder use case (e.g. a real
gitea-like app or test harness wanting to serve git) — until one
exists, the embedding seam is designed by example (alkgitd) only.
Tracker task: `tasks/architecture/oq-03-embedder.md`.
- **Cross-references**: ADR-001, ADR-002, transport.md §public API,
ssh.md §russh
- **Impacts**: blocks the first publish only, not implementation.
- **Blocked on**: first-publish timing (a release decision, not an
architecture question). The API surface inventory lives in
[backend.md](backend.md) §public API and [transport.md](transport.md)
§public API.
- **Cross-references**: ADR-010, ADR-002, backend.md, transport.md
## Theme: transport / protocol
@@ -109,7 +117,7 @@ when their impacts say so; it records how careful the resolution must be.
ingestion composition is not obvious from POC-2's findings. Tracker
task: `tasks/architecture/oq-04-receive-pack.md`.
- **Cross-references**: ADR-003, ADR-004, ADR-009, transport.md
§receive-pack, storage.md §ref transactions, http.md
§receive-pack, backend.md §"The trait family" (GitRefs), doors.md
### OQ-05: sha256 support policy
@@ -126,106 +134,59 @@ when their impacts say so; it records how careful the resolution must be.
## Theme: identity / auth
### OQ-08: Identity sources per front door (v1 auth mechanics)
### OQ-08: Registry identity space + vault placement (narrowed)
- **Origin**: [overview.md], [http.md], [ssh.md], [alkgitd.md]
- **Status**: open
- **Origin**: [overview.md], [doors.md], [backend.md]; originally "identity
sources per front door"
- **Status**: open — narrowed by ADR-010. Door auth mechanics (http token
handling, ssh keys) belong to the door crates; what remains for alkgit
is: the identity model the `GitRegistry` knows (what an identity is,
what identity records exist, whether they live in the same metadata
store as repo records — OQ-06's field list may grow), and where
credential material lives (alkvault; metadata holds references only).
- **Door type**: two-way
- **Priority**: high
- **Impacts**: blocks http.md and ssh.md auth sections finalizing; blocks
the identity-extractor callback design in ADR-006's seam; blocks
alkgitd config schema and the OQ-07 admin op shapes.
- **Resolution path**: decide per door — http (bearer token? basic? via
alkvault-stored credentials), ssh (russh-terminated public-key auth →
alkgit identity mapping per ssh.md), and which identities exist in
v1's registry — including whether identity records live in the same
metadata store as repo records (OQ-06's field list may grow for this).
- **Cross-references**: ADR-006, ADR-007, OQ-06 (metadata backing where
identity records live),
http.md §auth, ssh.md §identity, alkgitd.md, OQ-07
- **Impacts**: blocks backend.md's registry trait field list finalizing;
blocks the admin-ops shapes (OQ-07).
- **Resolution path**: one focused session on the registry identity model
+ alkvault placement.
- **Cross-references**: ADR-007, ADR-010, OQ-06, OQ-07, backend.md
§GitRegistry, doors.md
## Theme: storage / metadata
### OQ-06: Registry/metadata backing store
### OQ-06: Registry/metadata backing store (gix feature)
- **Origin**: [storage.md], ADR-008
- **Origin**: [backend.md] (was storage.md), ADR-008
- **Status**: deferred(scope)
- **Door type**: two-way (backing choice is swappable behind the core
trait)
- **Priority**: high for v1 config story, but choice deferrable because
the trait boundary is what matters
- **Impacts**: blocks storage.md's registry section finalizing and
alkgitd's config schema; does NOT block core/transport work (they code
against the trait).
- **Door type**: two-way (backing choice is swappable behind the
`GitRegistry` trait)
- **Priority**: high for the gix feature's default story, but choice
deferrable because the trait boundary is what matters
- **Impacts**: blocks backend.md's gix-feature registry impl finalizing;
does NOT block the wire layer (it codes against the trait).
- **Blocked on**: concrete metadata-scale requirements (how many repos,
what metadata fields beyond id/root/visibility/ACL scope, whether
alkcall-hub integration lands in v1). A config-file or embedded-store
decision without those inputs would be a guess. Tracker task:
identity records join — OQ-08's output, whether hub integration lands
in v1). A config-file or embedded-store decision without those inputs
would be a guess. Tracker task:
`tasks/architecture/oq-06-metadata-backing.md`.
- **Cross-references**: ADR-008, storage.md §registry, alkgitd.md §config,
OQ-08 (whose identity-records question may extend this store's field
list)
- **Cross-references**: ADR-008, ADR-010, backend.md §"The trait family" (GitRegistry), OQ-08
(whose identity-records question may extend this store's field list)
### OQ-07: Admin API operation set (v1 scope)
- **Origin**: [overview.md], [alkgitd.md], alk-stack.md §"The gitea lesson"
- **Status**: open
- **Origin**: [overview.md], alk-stack.md §"The gitea lesson"
- **Status**: open — rescope note (ADR-010): there is no alkgit binary, so
there is no alkgit-owned admin surface. The question narrows to whether
the gix feature's `GitRegistry` implementation ships with any management
ops (repo create/delete, visibility, ACL grant) as reusable alkcall ops,
or whether registry mutation is entirely downstream assembly work. If
shipped, they are `Visibility::Internal` alkcall ops over the
assembler's admin listener — never the git traffic surface.
- **Priority**: medium
- **Impacts**: blocks the admin-ops inventory (repo create/delete,
visibility set, ACL grant/revoke, user/identity management — if v1 has
users at all, which is OQ-08 territory). Deliberately small: everything
is `Visibility::Internal` alkcall ops over the admin surface.
- **Resolution path**: one dedicated session once OQ-08's identity model
exists (the ops' shapes depend on what identities/credentials mean).
- **Cross-references**: ADR-007, OQ-08, alkgitd.md §admin API
### OQ-09: Slim-crate model — doors as family infrastructure, git as a payload service
- **Origin**: user session (2026-09-21); ADR-006, ADR-001, ssh.md
- **Status**: open — under discussion, no commitment
- **Door type**: one-way once anything is published (crate deletion and
feature-promotion are wire/registry-visible to dependents)
- **Priority**: high
- **Impacts**: blocks ADR-006 finalization (OQ-01 is subsumed by this if
Slim-B is chosen); reshapes ADR-001 (crate set), ssh.md (delete or
defer), OQ-08 (narrows: git consumes door identity), alkgitd's assembly
surface, and publish sequencing (alkhttp `git` feature requires
alkgit-transport on crates.io first).
- **Context**: the alk family shares doors — alkhttp exists, alkssh is
planned (after alksocks), alknet rewrite coming. Doors wrap alkcall
producer/consumer in their wire protocol; services (git, tty, tunnels,
socks) are payloads doors optionally expose. git should be a service
exposed by downstream consumers rather than owning its own doors.
Candidate end-states: **Slim-A** — keep alkgit-http factory, 5-crate
layout, ADR-006 Option A as written; **Slim-B** — protocol crates +
alkhttp `git` feature for smart-http (requires alkgit published first);
**Option C (pure protocol crate)** — follow the alktty/alktunnels
template exactly: single `alkgit` crate, producer half (`GitAdapter`
for `alk/git` ALPN + channels `register_openable`, POC-1 substrate),
consumer half (typed `GitSession` — the replication/mirroring
primitive), backend traits (registry/refs/pack-gen/pack-ingest) with
the gix implementation feature-gated (mirrors alktty's `local`; NOT a
wasm goal, the cleanliness just falls out), smart-http stateless
substrate stays in-crate while the http mounting becomes alkhttp's
`git` feature. No binary; assembly is downstream's. Consequences to
record on commitment: vision.md's "single-binary git server" framing
is amended (assembly belongs to downstream consumers); ADR-001/006
superseded by a new ADR; ssh.md defers entirely to alkssh (the
temporary russh scaffolding decision is dropped, not shipped); OQ-08
narrows to the registry identity space + vault placement.
- **Sub-decisions**: (1) delete `alkgit-ssh` now and defer git-over-ssh to
alkssh, or keep temporary russh scaffolding in alkgitd until then
(hinges on whether our deployment needs git-over-ssh before alkssh
lands); (2) http adapter home — Slim-A (keep alkgit-http, ADR-006
Option A as written) vs Slim-B (alkhttp 0.6 `git` feature; requires
alkgit published first); (3) crate granularity — keep core+transport
split (embedders wanting storage-only) vs merge into one `alkgit`
(Option C's shape); (4) under Option C, backend-trait surface: full
family (registry, refs, pack-gen, pack-ingest) vs minimal (pack +
registry, refs ride the registry trait); (5) alkgitd's fate — deleted,
or kept as a thin reference assembly once doors exist to assemble
against.
- **Resolution path**: dedicated discussion session; decide the three
sub-decisions, then rewrite ADR-001/006 and ssh.md (a new ADR superseding
the affected parts, per ADR stability rules), and re-scope OQ-08.
- **Cross-references**: OQ-01, OQ-03, OQ-08, ADR-001, ADR-006, ssh.md,
alkgitd.md
- **Impacts**: blocks the gix feature's registry impl scope; OQ-08's
identity model determines the op shapes.
- **Resolution path**: decide alongside OQ-08 (one session can settle
both).
- **Cross-references**: ADR-007, ADR-010, OQ-08, backend.md §"The trait family" (GitRegistry)
+61 -85
View File
@@ -7,113 +7,89 @@ last_updated: 2026-09-21
## Purpose
alkgit is a self-hosted git server: repository storage, the git smart
protocol served over http and ssh interfaces, and the `alkgitd` binary that
assembles it all. It exists to provide a small, security-first git platform
whose exposure model inverts the mainstream pattern (visible-surface =
authorized-surface, authenticated-by-default, no plaintext secrets, no
plugin execution). See `docs/research/vision.md` for the full WHY.
alkgit is the git payload service of the alk family: a pure protocol crate
(per ADR-010, following the alktty/alktunnels template) implementing the
git smart protocol over alkcall channels — the `alk/git` ALPN. It provides
repository storage as backend traits (with a feature-gated gitoxide
implementation), the git smart protocol as producer/consumer halves, and
nothing else: no binary, no front doors. The original framing of this repo
(a monorepo with an `alkgitd` binary and its own http/ssh crates) was an
init-agent artifact corrected by OQ-09/ADR-010; `docs/research/vision.md`
is amended accordingly.
## The one-line architecture
**Storage + ACL + protocol adapters.** `alkgit-core` owns repository storage
and access-rule types; `alkgit-transport` owns the git protocol state
machines; `alkgit-http` and `alkgit-ssh` are thin front doors that
authenticate, resolve the repo, and hand a session to the transport;
`alkgitd` assembles everything with config, TLS/ACME, and the vault.
**One protocol crate: producer + consumer + backend traits, gix behind a
feature.** Doors (alkhttp, alkssh, alknet) expose it; assembly belongs to
downstream consumers.
## Crate map
| Crate | Owns | Depends on | Never depends on |
|---|---|---|---|
| `alkgit-core` | repo registry/storage roots, odb wrappers, ref store, pack generate/ingest, fsck, ACL input types | gix crates, alkcall (ACL types only) | any front-door crate |
| `alkgit-transport` | pkt-line sessions, V2 advertisement/state machine, ls-refs, fetch, receive-pack | `alkgit-core`, alkcall, gix-packetline | alkhttp, alkgit-ssh |
| `alkgit-http` | smart-http endpoints over alkhttp | transport, core, alkhttp | alkgit-ssh, alkgitd |
| `alkgit-ssh` | git-command dispatch for exec requests over alkcall channels | transport, core, alkcall | alkhttp, alkgitd |
| `alkgitd` | binary: config, assembly, TLS/ACME, listeners, vault wiring | everything | — |
Single crate `alkgit`:
Dependency rules (ADR-001, ADR-002):
| Half | Contents | POC evidence |
|---|---|---|
| Producer | `GitAdapter` (`alk/git` ALPN via alkcall `ProtocolHandler`), channels `register_openable` (repo id in open-op params — the negotiation + ACL point) | POC-1 verbatim |
| Consumer | `GitSession` typed client (`connect_direct`, `open_via_channels`) — the replication/mirroring primitive for alknet | new, small (TtySession analog) |
| Substrate | duplex session + stateless request/response layer (ADR-005); wire framing, V2 state machines (ADR-003) | POC-1, POC-3 |
| Backends | `GitRegistry`, `GitRefs`, `GitPackGen`, `GitPackIngest` traits; gix impl behind the default-on `gix` feature | POC-2 (gix impl) |
1. `alkgit-core` + `alkgit-transport` are **front-door-blind**: they consume
(identity, repo id, duplex stream, limits) and depend on alkcall types
only. No http types, no ssh channel types below the stream.
2. `alkgit-http` and `alkgit-ssh` never depend on each other.
3. `alkgitd` is the only crate allowed to know the whole graph.
4. A downstream application (gitea-like) embeds core + transport and brings
its own front doors; the admin API is a set of alkcall ops it may use or
replace.
Feature model: `default-features = false` gives the wire/protocol layer
without gix (wasm-clean as a side effect, not a goal); the `sha256`
passthrough and (eventually, in alkhttp) the `git` door feature ride the
same pattern. Doors live in the door crates — see [doors.md](doors.md).
## Security invariants (spec-level, all components honor)
## Security invariants (spec-level, carried from vision/principles)
These are the load-bearing rules from `docs/research/vision.md` and
`docs/research/alk-stack.md`; each component doc references them where
concrete:
1. **Authenticated by default** — anonymous fetch exists only on
explicitly-public repos; push is always authenticated.
2. **Visible-surface = authorized-surface** — via alkcall ACL; ops with
`Visibility::Internal` are never wire-callable; admin ops ride an admin
surface, never the git traffic surface.
3. **ACL before advertisement** — access is checked before any ref line or
capability line is emitted (ref names leak repo existence) (ADR-007).
4. **Registry-resolved repo identity** — wire-supplied repo names are IDs
resolved to server-configured storage roots; never used as paths (ADR-008).
5. **No secret material on the wire or at rest outside alkvault** — metadata
holds vault references only; outbound credentials flow through alkcall
`Capabilities` (alkcall ADR-010), and no handler reads credentials from
env or files (the no-env-vars invariant).
6. **No shelling out to `git`** — serving path is pure Rust on gix
primitives (GPL hygiene + no process-injection surface).
7. **Bounded resources** — every session carries wall-clock, size, and round
limits (ADR-009); unbounded loops/buffers are bugs.
8. **Honest capability advertisement** — the protocol advertises exactly
what we serve (ADR-003).
## Interfaces (the boundary shape)
The core boundary, from POC-1 (`docs/research/poc-1-findings.md`, follow-up 4) and the
"ALPN as a service" rule in `docs/research/vision.md`:
- **Transport input**: one session = (peer identity from alkcall
`AuthContext`, repo id resolved against the registry, a duplex byte
stream, session limits). For the stateless http door this becomes
(identity, repo, request-reader, response-writer, limits) — the same state
machines, a different substrate (ADR-005).
- **Storage input**: the transport asks core for (a) ref advertisement data,
(b) pack generation for a want/have set, (c) pack ingestion + ref
transactions for receive-pack. Storage never sees pkt-lines.
1. **Authenticated by default** — anonymous fetch only on explicitly-
public repos; push always authenticated.
2. **Visible-surface = authorized-surface** — alkcall ACL end-to-end;
internal ops never wire-callable.
3. **ACL before advertisement** — nothing is emitted before the check
(ADR-007); the channels open-op carrying the repo id is the natural
enforcement point on the native path.
4. **Registry-resolved repo identity** — wire names are ids, never paths
(ADR-008).
5. **No secret material on the wire or at rest outside alkvault** —
metadata holds vault references; no env-var credential reads.
6. **No shelling out to `git`** — pure Rust on gix primitives.
7. **Bounded resources** — every session carries `Limits` (ADR-009).
8. **Honest capability advertisement** — advertise exactly what we serve
(ADR-003).
## What is already validated (POC-backed)
- pkt-line over alkcall `BiStream` end-to-end (POC-1).
- Pack generation pipeline streaming with O(counts) memory (POC-2).
- Smart-http streaming both ways through alkhttp custom routes (POC-3).
- pkt-line over alkcall `BiStream` end-to-end (POC-1 → producer half).
- Pack generation pipeline streaming with O(counts) memory (POC-2 → gix
backend impl).
- Smart-http streaming both ways (POC-3 → the stateless substrate that
alkhttp's future `git` feature maps onto; `docs/research/poc3-findings.md`
§alkhttp fit is the mounting reference).
The full V2 fetch path against real git 2.43 is proven; receive-pack is
designed but not yet exercised (OQ-04 tracks the POC/validation gap).
designed but not yet exercised (OQ-04).
## Design Decisions
| ADR | Decision | Summary |
|---|---|---|
| [001](decisions/001-crate-decomposition.md) | Crate decomposition | 5 crates: core, transport, http, ssh, alkgitd |
| [002](decisions/002-front-door-blind-core.md) | Front-door-blind core | Session boundary = (identity, repo, stream, limits) |
| [003](decisions/003-protocol-v2-first.md) | V2-first protocol | V2-only fetch on both doors; honest advertisement; multi-round negotiation sequenced (OQ-02); push surface OQ-04 |
| [004](decisions/004-pack-pipeline.md) | Pack pipeline | gitoxide `data::output` generation, streaming-input ingestion |
| [005](decisions/005-session-substrate-types.md) | Substrate types | Duplex + stateless session APIs over one state machine |
| [006](decisions/006-http-adapter-composition.md) | HTTP adapter composition | Router factory in alkgit-http (**proposed**, OQ-01) |
| [007](decisions/007-acl-before-advertisement.md) | ACL first | No ref/capability line before ACL passes |
| [008](decisions/008-registry-resolved-repo-identity.md) | Repo identity | Wire names are registry IDs |
| [009](decisions/009-bounded-resources-budget.md) | Budgets | Every session carries limits |
| [001](decisions/001-crate-decomposition.md) | Crate decomposition | **superseded by ADR-010** |
| [002](decisions/002-front-door-blind-core.md) | Session boundary | (identity, repo, stream, limits) — unchanged, load-bearing |
| [003](decisions/003-protocol-v2-first.md) | V2-first protocol | V2-only both doors; honest advertisement |
| [004](decisions/004-pack-pipeline.md) | Pack pipeline | `data::output` gen / `data::input` ingestion |
| [005](decisions/005-session-substrate-types.md) | Substrate types | duplex + stateless APIs over one state machine |
| [006](decisions/006-http-adapter-composition.md) | HTTP adapter composition | **superseded by ADR-010** (mounting → alkhttp feature) |
| [007](decisions/007-acl-before-advertisement.md) | ACL first | no ref/capability line before ACL passes |
| [008](decisions/008-registry-resolved-repo-identity.md) | Repo identity | wire names are registry IDs |
| [009](decisions/009-bounded-resources-budget.md) | Budgets | every session carries limits |
| [010](decisions/010-pure-protocol-crate.md) | Pure protocol crate | single crate, producer/consumer halves, no doors/binary |
## Open Questions
Key cross-cutting questions tracked in [open-questions.md](open-questions.md):
Key questions tracked in [open-questions.md](open-questions.md):
- **OQ-01**: http adapter composability (where the smart-http routes live
for downstream embedding) — affects http.md and ADR-006.
- **OQ-04**: receive-pack (push) validation gap (high — the
always-authenticated half of the wire surface).
- **OQ-08**: identity sources per front door (how http and ssh authenticate
peers in v1).
- **OQ-06**: metadata store backing for the registry (config-file vs
embedded store vs alkcall-hosted).
- **OQ-06**: registry backing store for the gix feature (deferred on
scale requirements).
- **OQ-08**: registry identity space + vault placement (narrowed by
ADR-010).
-106
View File
@@ -1,106 +0,0 @@
---
status: draft
last_updated: 2026-09-21
---
# alkgit-ssh: SSH Front Door
## What it is
The ssh adapter: serves the git command surface over ssh. Unlike http,
ssh is session-shaped, so it maps directly onto the transport duplex
session (ADR-002).
**Client compatibility (v1 decision):** stock git clients
(`git clone ssh://git@host/repo`) speak the SSH wire protocol — an exec
request over an SSH connection. alkcall channels are *not* that protocol;
they are alkgit's internal session layer. v1 therefore terminates real SSH
wire protocol (russh is the candidate); the vision's "not a general sshd"
non-goal is untouched — only the git command surface is served. Concretely:
- `alkgitd` terminates SSH (russh under the `ssh` door) and hands the
adapter the post-auth exec request + stream.
- alkcall channels remain the *internal* substrate identity/ACL flows
through (alk-stack.md §"Interfaces in alkcall terms"); the adapter's
dispatch output is the same duplex session either way.
- A pure-alkcall-channels ssh door (no wire SSH) is a downstream-embedding
variant only — OQ-03 territory, not v1.
- Manifest note: the pre-phase-1 workspace skeleton pins only `alkcall` in
`crates/alkgit-ssh/Cargo.toml`; the russh dependency (and where it sits —
alkgitd vs alkgit-ssh) is set when this spec implements.
## What we serve
Exactly the git command surface, not a general sshd (vision non-goals):
| Command | Serves |
|---|---|
| `git-upload-pack '<repo>'` | fetch/clone (duplex V2 session) |
| `git-receive-pack '<repo>'` | push (duplex receive-pack; OQ-04) |
| `git-upload-archive` | **not served** (declined explicitly —
`docs/research/git-protocol.md` §security notes; honest refusal, not
silence) |
## Session flow
1. **Command dispatch**: parse the exec request string (`git-upload-pack`
/ `git-receive-pack` + quoted repo argument). Reject other commands
with a clear error on stderr (no shell interpretation — the command
string is parsed, never executed).
2. **Repo resolution + ACL** (ADR-007/008): the quoted repo argument is a
registry ID; resolve, authorize (read for upload-pack, write for
receive-pack — push always authenticated), all before any protocol
byte.
3. **V2 negotiation**: `GIT_PROTOCOL=version=2` arrives as an ssh env
line (`git-protocol.md` §framing). v1 policy: expect/require V2 per
ADR-003 (V0/V1 declined with a clear error).
4. **Handoff**: the post-auth stream (from the russh-terminated session)
is the duplex substrate; transport runs the duplex session
(advertise-once → command loop, sideband packs). The adapter's job
ends here — no protocol logic lives in this crate.
## Identity
- With russh terminating SSH (see the v1 decision), peer identity comes
from the ssh authentication the terminator performs (public key,
typically). How that identity becomes an alkgit/alkcall identity — and
where authorized keys live (vault involvement) — is **OQ-08**, shared
with http; one auth session settles both doors.
## Relationship to russh
russh terminates the SSH wire protocol for stock git clients (see the v1
decision above). The dispatch + ACL + handoff sequence (§Session flow) is
door-agnostic: alkcall-channel and russh inputs converge on the same
transport duplex session. Whether alkgit-ssh exports a russh-flavored
adapter separately from the alkcall one is an OQ-03 (embedding surface)
question — v1 ships the path alkgitd needs (russh termination → this
adapter's dispatch).
## Limits
The duplex session carries `Limits` (ADR-009). Channel-level caps
(alkcall ADR-041 per-identity channel cap) still apply above us; git
sessions consume one channel each (alk-stack.md §"Interfaces in alkcall
terms").
## Design Decisions
| ADR | Decision | Summary |
|---|---|---|
| [001](decisions/001-crate-decomposition.md) | Crate decomposition | ssh is a separate replaceable adapter |
| [002](decisions/002-front-door-blind-core.md) | Session boundary | duplex session = native ssh shape |
| [003](decisions/003-protocol-v2-first.md) | V2-first | V0/V1 declined via env-line check |
| [007](decisions/007-acl-before-advertisement.md) | ACL first | before advertisement, push always authed |
| [008](decisions/008-registry-resolved-repo-identity.md) | Repo identity | exec argument = registry ID |
## Open Questions
- **OQ-08**: ssh identity mechanics (open).
- **OQ-03**: russh-flavored embedder adapter (deferred(scope)).
## References
- `docs/research/poc-1-findings.md` (duplex session shape, git:// in-band framing
analog — ssh exec is the same resolve-before-emit order)
- `docs/research/alk-stack.md` §"Interfaces in alkcall terms"
-117
View File
@@ -1,117 +0,0 @@
---
status: draft
last_updated: 2026-09-21
---
# alkgit-core: Storage
## What it is
The repository storage layer: repo registry, object database wrappers, ref
store, pack generation/ingestion, and access-rule input types. It is
transport-agnostic and front-door-blind (ADR-001): it knows nothing about
pkt-lines, http, or ssh.
## Why this shape
gitoxide provides the primitives (odb, packs, refs, fsck, objects); the
generation-pipeline decision is ADR-004 (POC-2-validated). The crate
exists to (a) wrap gix with a coherent session-safe API (`gix_odb::Cache`
is not `Sync` — the sharing rules are enforced by the API in this crate),
(b) own the registry (ADR-008), and (c) hold the ACL input types the
adapters and alkcall share.
## Components
### Repository registry
- Authoritative (repo id → storage root, visibility, ACL scope) mapping.
- Wire repo names are registry IDs, never paths (ADR-008); resolution
failure is indistinguishable from authorization failure (ADR-007).
- Backing store: **open** — OQ-06. Core defines the trait; alkgitd picks
the backing. Metadata holds no secrets (vault references at most).
- Storage roots are server-configured; the registry maps IDs onto them.
### Repository access (`Repo`)
- Opens a (storage root, hash algo) into an object database + ref store.
- Wraps `gix_odb::Store` sharing: `Arc<Store>` shared across sessions,
per-session handles (`to_handle_arc()` + `prevent_pack_unload()` +
`ignore_replacements = true` — the POC-2 prerequisites). The public API
hands out handles, never the raw `Cache`.
- Ref listing for advertisement: refs + peeled tags + symref targets, the
ls-refs response data.
- Ref transactions: CAS apply for receive-pack (`gix-ref` transaction
module), with name validation (git ref rules + reserved-namespace
deny-list; `docs/research/git-protocol.md` §security notes).
### Pack generation (fetch side)
- Type: (odb handle, wants, haves, limits) → streaming pack (`io::Write`
consumer). Negotiation-agnostic: negotiation produces the boundary sets,
the writer streams them (ADR-004).
- Encodes the POC-2 composition: tip peeling → commit-ancestry walk →
`TreeContents` count → entries → bytes; missing objects abort (never
emit a broken pack); entry statistics surface as metrics.
- Memory profile is O(counts) — the budget model (ADR-009) bounds
aggregate work, not internal buffering.
### Pack ingestion (receive side)
- Client pack stream → indexed pack + fsck/connectivity report + per-ref
CAS application. Uses `gix-pack` streaming-input + `gix-fsck` +
`gix-ref` transactions. Shape is designed (ADR-004); validation against
real `git push` is pending — OQ-04.
- Received pack size is budgeted (ADR-009 max pack size); ingestion runs
on blocking threads like generation.
### Access-rule input types
- The types alkcall `AccessControl::check` consumes for repos: visibility
(public/private), identity-based read/write. Deliberately *input types*:
the evaluation function lives in alkcall (single source of authorization
logic); invocation/wiring happens in the adapters (ADR-007). Core
defines what a repo's rule set looks like.
- v1 scope: repo visibility + identity read/write, nothing richer
(vision §"Primary deployment target").
## Concurrency model
- `Store`/registry structures: `parking_lot` short-held locks (project
convention 3).
- odb handles: per-session, owned, moved into `spawn_blocking` tasks.
- Ref transactions: serialized per-ref by gix-ref's lock files; the
registry mapping's read-mostly synchronization (ArcSwap-class or
equivalent) is part of OQ-06's backing decision.
- Poisoned locks: `unwrap_or_else(|e| e.into_inner())` per convention 2.
## Public API surface (v1)
`lib.rs` re-exports (module structure per convention 14): registry trait +
types, `Repo`/handle types, pack generate/ingest types, ref-transaction
API, ACL input types, error enums (`thiserror`, no panics, no unwrap
outside tests). The embedder-facing freeze point is tracked in OQ-03.
## Design Decisions
| ADR | Decision | Summary |
|---|---|---|
| [001](decisions/001-crate-decomposition.md) | Crate decomposition | core = storage, transport-blind |
| [004](decisions/004-pack-pipeline.md) | Pack pipeline | `data::output` generation, `data::input` streaming ingestion |
| [007](decisions/007-acl-before-advertisement.md) | ACL before advertisement | core supplies rule inputs, adapters enforce |
| [008](decisions/008-registry-resolved-repo-identity.md) | Repo identity | wire names are registry IDs |
| [009](decisions/009-bounded-resources-budget.md) | Budgets | limits flow into generation/ingestion |
## Open Questions
- **OQ-06**: registry backing store (deferred(scope) — blocked on
metadata-scale requirements).
- **OQ-04**: receive-pack ingestion validation (deferred(unclear) — pieces
decided, push state machine needs a walkthrough/POC).
- **OQ-03**: embedder-facing API freeze (deferred(scope)).
## References
- `docs/research/gitoxide.md` (the API contract notes are normative here)
- `docs/research/poc2-findings.md` (generation pipeline + prerequisites)
- `docs/research/git-protocol.md` §"Server-side pack generation"
+44 -29
View File
@@ -3,28 +3,34 @@ status: draft
last_updated: 2026-09-21
---
# alkgit-transport: Git Smart Protocol
# alkgit: Git Smart Protocol (wire layer)
## What it is
The git protocol layer: pkt-line session substrates, protocol V2 state
machines (advertisement, ls-refs, fetch, receive-pack), and the glue that
keeps adapters from touching `futures_io` or raw packetline APIs. Depends
on `alkgit-core` and alkcall types only (ADR-001/002).
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** (ssh, git://, embedded 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) — 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).
- **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.
@@ -40,12 +46,15 @@ substrate property (per-request state), not a protocol fork.
`fetch=wait-for-done` for the v1 done-path policy — ADR-003 pins the
values; OQ-02 may extend them when multi-round lands,
`object-format=sha1`). Unimplemented features are declined by omission
(validated against real git, POC-1).
(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 core's listing, flush. `ref-prefix` filtering is client-driven.
from the backend's listing, flush. `ref-prefix` filtering is
client-driven.
### fetch
@@ -53,10 +62,10 @@ substrate property (per-request state), not a protocol fork.
POC-1 to-do).
- Negotiation policy (v1 initial): full-closure pack on `done`
(POC-validated). Multi-round ack/NAK negotiation: **OQ-02**.
- Pack generation via core (ADR-004), streamed over sideband on duplex /
sideband-in-response on http; generation runs on `spawn_blocking` with
the owned odb handle moved in (POC-2's shape: store shared, handle per
session, generation on blocking threads).
- 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).
### receive-pack (push)
@@ -66,8 +75,9 @@ substrate property (per-request state), not a protocol fork.
transactions, emit status report. The capability set, shallow policy,
CAS timing, and exact status-report shape are OQ-04's investigation
target (design intent: shape per OQ-04's resolution).
- Validation pending — **OQ-04** (includes the receive-pack version
surface, which ADR-003's fetch-V2 decision does not cover).
- Ingestion via `GitPackIngest` (ADR-004); validation pending — **OQ-04**
(includes the receive-pack version surface, which ADR-003's fetch-V2
decision does not cover).
### Error taxonomy
@@ -83,10 +93,11 @@ round, receive-pack max size, wall clock, sideband chunk size (fixed
## Public API surface (v1)
`lib.rs` re-exports: `Session` (duplex) + `StatelessRequest` (http)
entry points, `Limits`, hook trait(s) connecting to core
(advertisement data, pack generation, ingestion), protocol error enums.
The exact embedder-facing freeze point: **OQ-03**.
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
@@ -97,18 +108,22 @@ The exact embedder-facing freeze point: **OQ-03**.
| [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 |
## Open Questions
- **OQ-02**: V2 multi-round negotiation (open — efficiency, not
correctness).
- **OQ-04**: receive-pack state machine + validation (deferred(unclear)).
- **OQ-03**: embedder API freeze (deferred(scope)).
- **OQ-03**: publish/API freeze (partially resolved — single-crate shape
settled by ADR-010).
- **OQ-05**: sha256 policy (deferred(scope)).
## 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/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/git-protocol.md` (inventory + observed corrections)
- `docs/research/gitoxide.md` §"Wire format" (packetline contracts)
- `docs/research/gitoxide.md` §"Wire format" (packetline contracts)
- alktty `wire.rs`/`session.rs`/`adapter.rs` (the template's half shapes)
+10 -2
View File
@@ -4,7 +4,7 @@ Phase 0 (exploration) research. Feeds phase 1 (architecture).
| Doc | Topic | Status |
|---|---|---|
| [vision.md](vision.md) | Vision, guiding principles, non-goals, phase-0 checklist | draft v1 |
| [vision.md](vision.md) | Vision, guiding principles, non-goals, phase-0 checklist | draft v2 (amended 2026-09-21)
| [gitoxide.md](gitoxide.md) | gitoxide (gix) capability + version alignment | initial pass complete |
| [alk-stack.md](alk-stack.md) | alk stack fit, integration surface, gitea-lesson constraints | initial pass complete |
| [git-protocol.md](git-protocol.md) | Server-side git smart protocol inventory (what we own) | initial pass complete |
@@ -27,4 +27,12 @@ Phase 0 (exploration) research. Feeds phase 1 (architecture).
Phase 0 is done when POC-1..3 have results and a recommended-approach
summary is written here (append below). Then the Architect produces
`docs/architecture/` per sdd_process.
`docs/architecture/` per sdd_process.
**Convergence (2026-09-21)**: phase 0 gates all pass (POC-1/2/3 proceed).
Phase 1 opened with `docs/architecture/`; the structural decision is
ADR-010 — pure protocol crate following the alktty/alktunnels template
(the v1 "monorepo + alkgitd binary" framing in these research docs was an
init-agent artifact, amended in vision.md v2). POC-1 maps to the producer
half, POC-2 to the gix backend implementation, POC-3 to the stateless
substrate consumed by alkhttp's future `git` feature.
+52 -39
View File
@@ -1,18 +1,23 @@
# alkgit Phase 0: Vision and Guiding Principles
**Status**: draft v1 — 2026-09-19
**Status**: draft v2 — 2026-09-21 (v1 2026-09-19; v2 amends the delivery
shape — see "Sub-crate shape")
**Phase**: 0 (exploration) — this document captures WHAT we are building and
WHY before architecture (phase 1) commits to HOW.
## Vision
A self-hosted, single-binary git server in Rust: `alkgitd` serves repositories
over **http** and **ssh** interfaces with an authenticated-by-default surface,
built on the alk stack (alkcall, alkhttp, alktls, alkvault) and gitoxide.
It exists because self-hosted git platforms (gitea/gitlab) are large
multi-component applications with a long-tail of exposed APIs, plaintext
secret storage, and web-UI attack surface — and because CVE-2026-59774
(gitea) demonstrated that a single logic bug in an internally-exposed API is
A git payload service for the alk family, in Rust: alkgit implements the
git smart protocol as a **pure protocol crate** on alkcall channels (the
`alk/git` ALPN) — storage behind backend traits (gitoxide implementation
behind a feature), producer/consumer halves, no binary, no front doors.
Doors are family infrastructure: alkhttp exposes smart-http, alkssh
(planned) will expose git-over-ssh, the alknet rewrite carries the native
path; downstream consumers assemble what they want. It exists because
self-hosted git platforms (gitea/gitlab) are large multi-component
applications with a long-tail of exposed APIs, plaintext secret storage,
and web-UI attack surface — and because CVE-2026-59774 (gitea)
demonstrated that a single logic bug in an internally-exposed API is
enough for full compromise of the host. Our blast-radius and exposure model
is designed from day one, not retrofitted.
@@ -27,10 +32,11 @@ is designed from day one, not retrofitted.
2. **Secret hygiene**: gitea stores secrets plaintext in its DB. alkgit uses
alkvault for anything credential-shaped; the metadata store holds vault
references, never plaintext secrets.
3. **Footprint**: a git server is a small program (storage + smart protocol +
two front doors). The platform features (issues, PRs, wikis, CI) are where
the CVEs live. alkgit v1 is deliberately just the git server; everything
else stays out.
3. **Footprint**: a git server is a small program (storage + smart
protocol + thin doors); the platform features (issues, PRs, CI) are where
the CVEs live. alkgit v1 is deliberately just the git service as a
protocol crate; everything else stays out (doors live in the door
crates — alkhttp, alkssh — per ADR-010).
4. **Language**: the whole serving path is Rust (memory-safe, no C deps in
the packet path).
@@ -62,42 +68,49 @@ is designed from day one, not retrofitted.
- Issues/PR/review features.
- Git LFS (later phase, separate decision).
- federation/replication between alkgit instances (later phase).
- Serving as a general sshd (only the git command surface).
- Serving as a general sshd (alkgit carries no ssh surface at all;
alkssh is the door).
- Windows as a serving platform (linux first; keep code portable-ish but
don't test it).
## Sub-crate shape (provisional, matches workspace skeleton)
## Sub-crate shape (amended 2026-09-21 by ADR-010)
| crate | role |
The v1 draft's table below was an init-agent artifact (monorepo + binary +
own doors); the corrected shape is the alktty/alktunnels pattern — see
`docs/architecture/decisions/010-pure-protocol-crate.md`:
| Piece | Role |
|---|---|
| `alkgit-core` | storage: repos, refs, odb, pack read/write, access-rule types |
| `alkgit-transport` | smart protocol: pkt-line sessions, advertise, ls-refs, fetch, receive-pack |
| `alkgit-http` | http front door (smart-http endpoints + admin API via alkhttp) |
| `alkgit-ssh` | ssh front door (git commands over alkcall channels) |
| `alkgitd` | binary: config, assembly, TLS/ACME, serving loops |
| `alkgit` (single crate) | wire layer (substrates, V2 state machines) + producer/consumer halves + backend traits |
| `gix` feature (default on) | gitoxide-backed implementation of the backend traits |
| alkhttp `git` feature (future) | smart-http mounting of the stateless substrate |
| alkssh (future family crate) | git-over-ssh door |
| downstream assembly | the actual deployment (config, listeners, TLS/ACME, vault) |
## Composability: "ALPN as a service"
alkgit is the git member of the alk "ALPN as a service" family — alktty,
alktunnels, and alksocks (socks5) follow the same pattern, and the alknet
mono-repo is being decomposed into exactly these pieces for rewrite. The
pattern's load-bearing rule: **`alkgit-core` + `alkgit-transport` never know
which front door is talking.** The transport layer consumes (identity, repo
pattern's load-bearing rule: **alkgit never knows which front door is
talking.** The protocol crate consumes (identity, repo
id, duplex stream, limits) and speaks git; everything above the stream is
the adapter's problem. That is what keeps alkgit composable for downstream
the door's problem. That is what keeps alkgit composable for downstream
use:
- A future gitea/gitlab-like application should be able to embed
`alkgit-core` + `alkgit-transport` (or talk to `alkgitd`) and add its own
UI/issues/PR layer without forking anything here.
- The admin API is a set of alkcall ops, not an embedded web framework —
a downstream app can either use it or replace it.
- Nothing in the core may reach upward into alkhttp/alkssh concerns
- A future gitea/gitlab-like application embeds `alkgit` (wire layer, with
its own backend implementation via the traits or the gix feature) and
adds its own UI/issues/PR layer without forking anything here.
- Registry management ops, if the gix feature ships any, are a set of
alkcall ops, not an embedded web framework — a downstream app can either
use them or replace them (OQ-07).
- Nothing in the crate may reach upward into alkhttp/alkssh concerns
(no http types, no channel types below the transport boundary).
The v1 reduction follows from this: **storage + ACL + protocol adapters**.
A simple static/template web UI for the public repo listing is explicitly
out of scope here (would be a separate downstream thing on top).
The v1 reduction follows from this: **wire layer + backend traits + one
gix implementation**. A simple static/template web UI for the public repo
listing is explicitly out of scope here (would be a separate downstream
thing on top).
## Primary deployment target
@@ -117,19 +130,19 @@ in use**. So:
- [x] alk stack fit + integration surface (`alk-stack.md`)
- [x] server-side protocol inventory (`git-protocol.md`)
- [x] license/reference policy (`reference-policy.md`)
- [ ] POC-1: real `git clone` over alkcall BiStream using gix-packetline
- [x] POC-1: real `git clone` over alkcall BiStream using gix-packetline
(validates BiStream fit + packetline async codec end-to-end)
- [ ] POC-2: server-side pack generation composition (bundle-write vs
- [x] POC-2: server-side pack generation composition (bundle-write vs
entries-to-bytes) for a small want/have set
- [ ] POC-3: http smart-endpoint streaming shape through alkhttp
- [x] POC-3: http smart-endpoint streaming shape through alkhttp
(chunked pack response, unbuffered receive-pack POST ingestion)
- [ ] Convergence: recommended approach summary feeding phase 1 architecture
- [x] Convergence: recommended approach summary feeding phase 1 architecture
## Immediate threat-model notes carried into architecture
- The admin API (repo/user/permission management) is alkcall
`Visibility::Internal` ops over an admin-only listener; it is never part
of the git traffic surface.
- Registry management ops, if the gix feature ships any, are alkcall
`Visibility::Internal` ops over an admin-only listener; they are never
part of the git traffic surface (OQ-07).
- Repo names arriving on the wire are registry IDs, never paths; storage
roots are configured server-side only.
- The advertisement phase runs ACL before the first ref line is emitted.
+9
View File
@@ -0,0 +1,9 @@
//! alkgit: git smart protocol for the alk family.
//!
//! Producer/consumer protocol crate on alkcall channels — the `alk/git`
//! ALPN (ADR-010, the alktty/alktunnels template). Phase 1 (SDD) has just
//! begun: this crate currently contains only the module skeleton markers;
//! the substrate layer, V2 state machines, and backend traits land per
//! `docs/architecture/` (see transport.md, backend.md, doors.md).
#![forbid(unsafe_code)]