# ADR-002: Front-door-blind core — the session boundary ## Status Accepted (tuple amended by ADR-016: the session is per-service — `service` ∈ {upload-pack, receive-pack} joins the tuple, selected at establishment on every path) ## Context The load-bearing composability rule ("ALPN as a service", `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. POC-3 proved the stateless-http variant: the same protocol state machines ran per-POST over (request-reader, response-writer) instead of a duplex stream. The question this ADR settles: what is the *interface* the core/transport exposes to adapters? Alternatives considered: - Adapters implement an alkcall-style `ProtocolHandler` themselves and call into transport with raw `BiStream` — pushes too much protocol responsibility (advertisement timing, framing errors) into every adapter. - Transport speaks alkcall `Connection` directly — forces stateless http (one request per connection) through a session-shaped API; wrong shape for http, and couples transport to alkcall connection lifecycle. - HTTP-specific abstractions in transport — violates the blind-core rule. ## Decision The transport exposes two session entry points, both consuming the same tuple (peer identity, resolved repo, service, limits) — the service (upload-pack vs receive-pack) is explicit at establishment on every path (ADR-016): doors select it by route/exec command; the native paths carry it in the open-op params or the in-band request line: 1. **Duplex session** (ssh, git://, any stream door): transport consumes a `BiStream`-shaped duplex byte stream (`AsyncRead + AsyncWrite + Unpin`, the alkcall `BiStream` contract, alkcall ADR-005/009) and runs the service-selected advertise-once → command-loop state machines (V2 advertisement for fetch; the V0 ref advertisement for push — both are server-emitted firsts, so the service must precede the stream). Adapters hand it over after ACL and repo resolution (ADR-007, ADR-008). 2. **Stateless session** (smart-http): transport consumes a (request-reader, response-writer) pair per http request and runs one command per invocation, matching smart-http's stateless framing; the door's route supplies the service even here (nothing in a POST body distinguishes the services before parsing — ADR-016). The same core state machines run under both entry points. 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. The `futures-io` bridging detail (`tokio_util::compat`), the split/compat glue, and the packetline stop-delimiter handling live inside transport — adapters never see `futures_io` types (POC-1 follow-ups 1–2, POC-3 follow-up 2). ## Consequences - The core+transport pair is embeddable with any front door; POC-1/3 are existence proofs of both shapes. - State machines are written once; http statelessness is a substrate property, not a protocol fork. - Transport depends on alkcall types only (`BiStream` shape, identity, limits) — no alkhttp, no channel types below the stream. - One cost: the stateless entry point needs explicit per-request limits (body budget, wall clock) since there is no session to amortize them (ADR-009). ## References - `docs/research/vision.md` §"ALPN as a service" - `docs/research/poc-1-findings.md` (duplex shape validated), `docs/research/poc3-findings.md` (stateless shape validated) - 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), ADR-016 (the service selection this document's tuple carries) - overview.md §"Crate map"