Files
alkgit/docs/research/alk-stack.md
T
glm-5.3-flash 201c7a1fce docs: D-1 remainder — stale Internal-ops framing and OQ list
Completes review 001 D-1 (vision.md's half landed with ADR-015):

- alk-stack.md gitea-lesson item 2: supersession note pointing at
  ADR-012 §3 / ADR-015 (repo ops are External, scope + manage grant —
  right mechanism, wrong axis).
- AGENTS.md lifecycle status: active OQ list updated (OQ-03
  partially resolved, OQ-05 deferred, OQ-16 deferred; OQ-04/06/08
  resolved via ADR-013/012/011).
- review 001: D-1 marked resolved.

All three stale statements were pre-decomposition landmines: research
docs are declared 'current source of truth' by AGENTS.md, so the
superseded admin-API design needed marking.
2026-09-29 08:30:45 +00:00

125 lines
6.9 KiB
Markdown

# Research: alk stack fit for a git server
**Status**: initial pass complete
**Date**: 2026-09-19
**Sources**: local sibling crates at `/workspace/@alkdev/{alkcall,alkhttp,alktls,alkvault}`
(all MIT OR Apache-2.0; alkcall/alkhttp/alktls published on crates.io,
alkvault published at 0.1.0).
## TL;DR
The alk stack covers the parts of a self-hosted git server that were
historically the attack surface: the HTTP and SSH front doors, the wire
framing, TLS/ACME, and secret storage. alkgit contributes the git brain
(storage + smart protocol + repo/permission model) and the server binary that
assembles everything. The architectural match with alkcall is unusually good:
**alkcall's `BiStream` is exactly the byte-stream abstraction git's
packetline-based protocol wants underneath it** — the smart protocol is a
pair of unidirectional streams with request/response framing, which maps
cleanly onto `BiStream::write_half` / `read_half`.
## How the pieces map
| alkgit need | alk crate | mechanism |
|---|---|---|
| Authenticated transport with per-peer identity | alkcall | `Connection`, `Identity`, `IdentityProvider` |
| Internal/external operation split | alkcall | `Visibility::Internal` ops are never wire-callable (ADR-017/024) |
| Per-repo/per-op authorization | alkcall | `AccessControl::check(peer_identity)` + `required_scopes` |
| HTTP front door for git smart-http + admin API | alkhttp | HTTP/1.1 + HTTP/2 serving, adapters for call protocol |
| TLS + ACME for the HTTP endpoint | alktls | rustls server configs, cert resolvers, ACME state machine |
| Encrypted storage of tokens/keys at rest | alkvault | AES-256-GCM vault |
| Byte streams under pkt-line | alkcall | `BiStream` handed to handlers via `ProtocolHandler::connected` |
## The gitea lesson, restated as alkgit design constraints
The pwned-gitea incident (CVE-2026-59774) was reachable because an internal
API was exposed over the internet regardless of auth — the openapi-spec
pattern of "describe everything, enforce somewhere" is the structural
antipattern. alkcall already fixes the API-exposure half: only ops the caller
has privileges for are even visible, and internal ops are structurally
unreachable from the wire. alkgit must extend the same philosophy:
1. **No unauthenticated surface by default.** Even `info/refs` advertisement
requires an authenticated session unless a repo is explicitly public.
Anonymous-clone is a per-repo opt-in, not a global default.
2. **Admin API = internal ops.** Repo/user/permission management rides the
call protocol as `Visibility::Internal` ops over the admin interface
(or via alkhttp with auth); there is no admin endpoint that exists on the
same unauthenticated surface as git traffic.
*(Superseded by ADR-012 §3 / ADR-015 for alkgit's repo ops: the ops are
`Visibility::External`, gated by scope + the per-repo `manage` grant —
the gitea lesson is served by the ACL (visible-surface =
authorized-surface), not by hiding the ops. "Right mechanism, wrong
axis.")*
3. **No plaintext secrets in the DB.** Credentials/tokens go through
alkvault; the metadata store holds references, not keys.
4. **Blast-radius thinking carries into the design**: single binary, no
plugin execution, no markdown renderer by default (a web UI, if it ever
exists, is a separate phase and separate threat model).
## Interfaces in alkcall terms
Git transport framing per interface:
- **SSH**: the alkcall channels protocol carries a channel whose stream is
the git pkt-line conversation. The client (`git clone ssh://...`) expects
an exec of `git-upload-pack '<repo>'`; with alkcall the "SSH channel-like"
layer provides the framing and identity, and we dispatch on the requested
command string. alkcall's channel model (channel 0 = call registry, data
channels = `BiStream`) maps to: one data channel per git session.
- **HTTP**: alkhttp hosts the smart-http endpoints (`GET
/<repo>/info/refs?service=git-upload-pack`, `POST /<repo>/git-upload-pack`,
`POST /<repo>/git-receive-pack`). Smart-http is stateless per request
(V0/V1) or one-shot (V2), which fits the http adapter model; the response
body is a pkt-line stream we generate.
Both interfaces converge on the same core: **a `BiStream`-like duplex
session + repo identity + caller identity** → `alkgit-transport` runs the
smart protocol and produces/consumes packs via `alkgit-core`. The interfaces
are thin adapters; all policy (auth, ACL, limits) lives before the transport
layer gets the stream. This is the same "ALPN as a service" shape as
alktty/alktunnels/alksocks: the git member of the family, one core, many
front doors, and nothing below the transport boundary knows or cares which
door produced the stream.
## What to verify before architecture commits (POC candidates)
1. **BiStream ↔ pkt-line fit**: run a real `git clone` handshake over an
alkcall `Connection` using `gix-packetline` async codec end-to-end.
**Verified (POC-1, 2026-09-20)**: real git 2.43 clones/fetches
fsck-clean over `Connection → BiStream → tokio-util compat →
gix-packetline`. See `poc-1-findings.md`.
2. **HTTP smart protocol shape**: confirm alkhttp's streaming response body
can carry a pack (chunked) and that request bodies stream in without
buffering the whole POST in memory (receive-pack can be gigabytes).
**Verified (POC-3, 2026-09-21)**: real git 2.43 clones/fetches
fsck-clean over `Connection → HttpAdapter → axum custom routes`;
request bodies stream (`BodyDataStream`, one hyper-read chunk per
`poll_next` — measured with a dribble probe and git's forced-chunked
POST path); pack responses stream under back pressure (bounded mpsc →
`Body::from_stream`, O(counts) RSS on a 60k-object clone). See
`poc3-findings.md`.
3. **alkcall backpressure/limits vs git sessions**: large clones are
long-lived, high-throughput, single-direction streams; confirm channels
buffer limits (ADR-040) don't fight pack streaming, or route git sessions
as raw duplex streams rather than chunk-framed channels if needed.
4. **alktls ACME**: config shape for the http endpoint; likely trivial.
## Composability boundary (what "ALPN as a service" implies here)
Same family pattern as alktty/alktunnels/alksocks (alknet decomposition):
one core that is front-door-blind, adapters that each speak one door. For
alkgit the boundary is concrete:
- `alkgit-core` + `alkgit-transport` depend on alkcall types only
(`BiStream`, identity/ACL types); they never depend on alkhttp or
alkgit-ssh, and carry no http/channel-specific types below the stream.
- The http and ssh crates are replaceable: a downstream app (gitea-like)
embeds the core + transport and brings its own front doors.
- Admin API = alkcall ops, usable as-is or replaced by a downstream app.
## Version pins in workspace manifests
- `alkcall 0.8` (published), `alkhttp 0.5` (published), `alktls 0.1`
(published), `alkvault 0.1` (published). The workspace root already pins
these; bump via workspace.dependencies when siblings release.