- backend.md: git/repo/update pinned as PATCH (omitted fields unchanged; present fields replaced wholesale) — R-3; 'already_exists' disclosure posture recorded (accepted create-scope oracle; resolve-side stays collapsed) — R-6; repo-id grammar pinned (owner/name segments, rejection- only parsing, percent-encoded flat record-file naming, two-placeholder http-route note) — R-5 - doors.md: alkssh hand-off tuple gains service + authorized-repo marker (ADR-016/D-3 amendment rounds missed this third tuple site) — R-4 - ADR-016: collapsed-space and broken code-span glitches — R-8 - AGENTS.md: lifecycle paragraph records both gate reviews complete and decomposition unblocked Verification: docs-only; cargo doc/test/clippy/fmt clean
147 lines
7.9 KiB
Markdown
147 lines
7.9 KiB
Markdown
---
|
|
status: reviewed
|
|
last_updated: 2026-09-30
|
|
---
|
|
|
|
# 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) resolving an alkcall `Identity` (the
|
|
identity-extractor seam that ADR-006 needed exists only in the door, where
|
|
it belongs). alkgit consumes the resolved identity and the registry
|
|
record: the per-repo check is alkgit-core's `authorize` policy function
|
|
(ADR-011, ADR-015 — public+read anonymous-first-class, write always
|
|
authenticated+granted, `manage` for repo administration), run at
|
|
ADR-007's step-3 position by every door and by the channels open-op
|
|
gate. There is no door-specific auth surface
|
|
in alkgit and no scope constant — per-repo grants replaced the
|
|
`tty:open`-style gate (the single-scope shape cannot express
|
|
anonymous-public fetch).
|
|
|
|
## 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`);
|
|
top-level flush is one-shot per response (the receive-pack sideband
|
|
report's inner flush lives inside band-1 and is part of the report
|
|
framing — ADR-013 §8; the rule governs protocol-level sections only);
|
|
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). Push framing per ADR-013 §12 (probe/Content-Length/chunked,
|
|
request/result content types).
|
|
|
|
## 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 the ADR-002 duplex tuple — `(identity, repo,
|
|
service, authorized-repo marker, post-auth stream, limits)` — to
|
|
alkgit's duplex session; the exec command is the service selector
|
|
(ADR-016's per-path service-establishment rule). `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 (`channels/git/sub`,
|
|
params `{repo, service}` — ADR-016: the params are the negotiation, the
|
|
service selector, and the open-time ACL point; the establisher resolves
|
|
the repo and runs `authorize` with the action the service selects) need
|
|
zero door code — POC-1 is that shape verbatim. On the direct-ALPN path
|
|
`GitAdapter` parses the git-daemon request line
|
|
(`git-upload-pack <repo>\0host=…\0\0version=2\0` /
|
|
`git-receive-pack <repo>\0host=…\0`) as the in-band preamble (ADR-016).
|
|
Any alkcall-speaking client (including alkgit's own consumer half,
|
|
`GitSession` — ADR-017's typed fetch/push client) can use either path.
|
|
This is the baseline path; the http/ssh doors are conveniences layered
|
|
on top for stock git clients, and the replicator path of the p2p
|
|
deployment (direct push/pull between replicators) rides it directly.
|
|
|
|
## 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 |
|
|
| [011](decisions/011-per-repo-authorization.md) | Per-repo authorization | `authorize` policy, grants in records (amended: ADR-015) |
|
|
| [012](decisions/012-registry-backing-and-ops.md) | Registry + ops | op registration surface, two op kinds (gate amended: ADR-015) |
|
|
| [013](decisions/013-receive-pack-state-machine.md) | receive-pack | V0-framed push advertisement per door, report framing |
|
|
| [014](decisions/014-v2-negotiation-ack-loop.md) | Negotiation | one round per POST; ack section is per-round, stateless |
|
|
| [015](decisions/015-manage-grant-and-op-gate.md) | Manage grant + op gate | `manage` tier, admin-OR-manage op gate |
|
|
| [016](decisions/016-native-session-preamble.md) | Native session preamble | `{repo, service}` open-op params, request-line preamble, service in the tuple |
|
|
| [017](decisions/017-consumer-half-git-session.md) | Consumer half | `GitSession` typed client — the direct-connection push/pull primitive |
|
|
| [018](decisions/018-backend-trait-signatures-and-storage-error-model.md) | Trait signatures + storage errors | backend-seam shapes pinned (the door-blind object-storage family) |
|
|
|
|
## Open Questions
|
|
|
|
- None. (OQ-08 resolved by ADR-011 — door auth mechanics stay here,
|
|
registry identity model settled; ADR-012 §3 pins the op registration
|
|
surface, its gate amended by ADR-015. OQ-16 — grant-key identity
|
|
namespace — lives at the identity-provider seam, not the door.)
|
|
|
|
## 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) |