alkgit 0.1.0

Git smart protocol for the alk family: pkt-line substrates, protocol V2 serving (upload-pack/receive-pack), producer/consumer halves on alkcall channels, and the backend seam traits — the shipped alkstore+alkblobs composition is the alkgit-substrate crate (ADR-010/025)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
---
status: stable
last_updated: 2026-10-23 (the wave-5 release gate — the trait family
  and the seam types are the core's published surface; the
  implementation track composes against it)
---

# Backend traits: the storage seam

**Re-baselined 2026-10-19 (ADR-019..023, the substrate round):** the
trait family and its pinned signatures stand unchanged; the
implementation story beneath them is now substrate-first (alkstore +
alkblobs — [substrate.md](substrate.md)). `registry-file` and the
on-disk odb engine are gone; the feature model and the record schema
changed accordingly. The seam decisions live in the ADRs; this doc is
the decomposer-facing spec.

## What this is

The payload side of the protocol crate: the traits a service deployer
implements (or consumes via `alkgit-substrate` — the shipped
composition, ADR-025) so
the git protocol can talk to storage. Per ADR-010 this mirrors alktty's
`TtyBackend` pattern — traits in the protocol crate, implementations
excluded from it (now structurally: the composition is a sibling crate,
ADR-025). 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, ADR-011/012, ADR-018)

Five traits, kept small and orthogonal — no substrate-crate types
appear anywhere in the protocol core, the traits, or the seam types;
gix component types appear only at/above the seam (`gix_hash::ObjectId`
in the seam signatures — ADR-018) and in the wire layer's toolkit
(gix-protocol/packetline):

1. **`GitRegistry`** — repo id → `RepoRecord` (visibility, grants keyed
   on the stable logical identity id, action set `{read, write, manage}`
   — ADR-011, ADR-015). Async `resolve`; the authoritative mapping
   (ADR-008); resolution failure is indistinguishable from
   authorization failure (ADR-007). Authorization is alkgit-core's
   policy function evaluated on the record (data in the backend, policy
   in core — an embedder with a foreign permission system maps its ACL
   into grants at resolve time).
2. **`GitRegistryStore: GitRegistry`** — the write supertrait
   (`put_repo`/`update_repo`/`remove_repo`; alknet ADR-035's read/write
   split shape). The `git/repo/*` ops wrap it (ADR-012 §3). Grants ride
   the record, so grant mutation is `update_repo` (convenience wrappers
   additive later, two-way) — which is also the app's promotion path
   (roles/teams compile to flat grants upstream; ADR-015 §6) and the
   shape a future replication sync would write through (ADR-015 §8).
3. **`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).
4. **`GitPackGen`** — (`&RepoRecord`, wants, boundary_haves, limits,
   sink: `Box<dyn io::Write + Send>`) → streaming pack
   (names per the pinned signature below — ADR-018 §4), plus
   `common_haves(repo, haves) -> recognized
   subset` for the negotiation ack loop (a have is recognized iff it
   exists in the object store and is a commit — the honest boundary:
   never ack what we cannot subtract; ADR-014). Negotiation-agnostic
   generation. Missing objects abort with an error, never a broken pack
   (ADR-004).
5. **`GitPackIngest`** — client pack stream → validated updates +
   ingested objects. Thin-pack bases resolve from the object store
   (push sends thin packs by default — ADR-013 §5); objects land under
   pin-bridge protection until the ref CAS lands (ADR-020 §2 — the
   substrate composition; an impl without pins owns a different
   landing story). It *prepares* the validated ref updates; the
   transaction itself is applied by `GitRefs` (single CAS home — ingest
   validates, refs commits; one transaction per push is what makes
   `atomic` correct — ADR-013 §7). The prepare binding carries
   `push_options: Option<PushOptions>` (owned, the ADR-018 §4 pinned
   shape — ADR-013 §11, review 001 N-4):
   the parsed per-push metadata, present only when `push-options` was
   negotiated — `None` until the config gate opens; alkgit parses and
   forwards the option pairs verbatim, never interprets them. Budgeted
   (ADR-009 max pack size); the impl runs its blocking work off the
   async executor (concurrency model below).

Minimal-vs-full was the open sub-question; resolved as **full family**
(the collapse-cost rationale lives with ADR-018's review trail) —
each trait is one or two methods plus types, and the seams are kept
independent so an implementer satisfies only what it has. `alkgit-substrate`
implements all five (ADR-019/025); a downstream with foreign storage
implements any subset and consumes only the protocol core itself
(depends on `alkgit`, which structurally carries no substrate —
ADR-025).

### Pinned signatures (ADR-018; the freeze-inventory shapes)

Unchanged by the re-baseline — POC-O and POC-X consumed exactly these
shapes with zero signature drift (ADR-019 §9). The registry pair is
ADR-012 §1 as amended. The object-storage family (ADR-018 §4 — pinned
there; this section is the mirror the decomposer implements from):

- **`GitRefs`**: `list_refs(&RepoRecord) -> Vec<RefLine>` (the full
  set — ref-prefix filtering is wire-side, client-driven per ls-refs
  grammar) and `apply_updates(&RepoRecord, Vec<RefUpdate>, atomic:
  bool) -> Vec<RefOutcome>` (one CAS transaction per call — the
  atomic-correctness home, ADR-013 §7; realized as the one-transaction
  row-level CAS in the substrate impl, ADR-019 §2).
- **`GitPackGen`**: `generate(&RepoRecord, wants, boundary_haves,
  limits, sink: Box<dyn io::Write + Send>)` (ADR-004's pipeline) and
  `common_haves(&RepoRecord, haves) -> Vec<ObjectId>` (the ADR-014
  ack/done-round seam: a have is recognized iff it exists *and* is a
  commit — one seam predicate applied at both points, the ack rounds
  and the done-round boundary alike; the odb-free wire layer refines
  nothing, ADR-010 — the earlier "the is-commit refinement is the wire
  layer's ACK-line rule" wording was drift, fixed by the wave-2 gate).
  The replication have-aware trim rides the `haves` plumbing (ADR-023).
- **`GitPackIngest`**: `prepare(&RepoRecord, updates: Vec<RefUpdate>,
  pack: Box<dyn io::Read + Send>, push_options: Option<PushOptions>,
  limits) -> PreparedPush` (prepare-only; the CAS application lives in
  `GitRefs::apply_updates` — ADR-013 §6–7). The `updates` are the
  command-derived ref updates the wire layer parsed (`expected` = old,
  `new` = new); the impl validates them — the connectivity walk from
  the `new` tips (ADR-020 §2) — and returns them verbatim as
  `PreparedPush.updates` (verbatim round-trip in v1, no impl-side
  filtering — name validation and CAS stay wire-side/backend-CAS-side;
  the wave-3 prepare-seam amendment).
- The `repo` parameter is `&RepoRecord` (ADR-018 §2): the wire layer
  resolves once (ADR-007 step 2) and the impls key their storage on
  `repo_id` — refs are rows keyed by it, objects are store entries under
  its namespace/liveness domain (ADR-019; there is no path in the
  record — `storage_root` is gone per ADR-019 §8, and no impl opens
  anything from it).
- Shared types (ADR-018 §5): `RefLine` (`oid: None` = the unborn
  symref line, the N-5 rider's shape), `RefUpdate`
  (`expected: None` = create, `new: None` = delete), `RefOutcome`
  (the per-ref CAS result — an outcome, not an error; the reason string
  is the client-displayed `ng` text), `PreparedPush`
  (`unpack: Result<(), String>` + the updates; pack-level validation
  failures land as the unpack ng leg, not `Err`), `PushOptions` (the
  verbatim `(key, value)` pairs — ADR-013 §11 / N-4's additive pin).
- Object ids are `gix_hash::ObjectId` — always-on in the manifest;
  hash-format-parameterized internally, so OQ-05's policy change
  survives without a trait change.
- Errors: traits 1–2 → `RegistryError` (re-scoped below); traits 3–5 →
  `StorageError` (ADR-018 §7 — the missing-object abort, malformed
  input, and io variants; CAS and fsck outcomes are values, not
  errors).

## Crate and feature model (ADR-025; supersedes ADR-021's graph)

The workspace has two published crates (ADR-025): the protocol core
`alkgit` and the shipped composition `alkgit-substrate` (the seam
above is the contract between them). Engine choice is an
assembly-level constructor statement, never a feature (alkstore
ADR-016's deployment honesty); features gate implementation code that
needs dependencies. The placement table:

| Crate :: feature | Gates | Deps | Default |
|---|---|---|---|
| `alkgit` :: `sha256` | hash-algorithm passthrough (OQ-05) | gix-hash/sha256 | off |
| `alkgit-substrate` :: `sqlite` | the sqlite co-tenant registry/ref adapter (the concrete tables + connections) | alkstore-sqlite | on |
| `alkgit-substrate` :: `postgres` | the postgres twin | alkstore-postgres | off |
| `alkgit-substrate` :: `lfs` | the LFS liveness/pointer-lifecycle surface (ADR-022) | — | on (fail-safe default — the rationale is ADR-022 §6) |

- **Reactivity and the engine-generic composition are
  `alkgit-substrate`'s base, not a feature** (the placement rule,
  ADR-025): the reactive halves — announce, wake runner, stream
  consumer, reconcile — ride the crate's unconditional deps (alkstore
  core trait + alkblobs; ADR-019 §5's no-non-reactive-shipped-path
  ruling). An embedder takes reactivity by taking the crate, or
  implements the seam itself against `alkgit` without the substrate
  crate at all — the all-or-nothing posture, now visible in the
  dependency graph.
- The former `substrate`/`sqlite`/`postgres` features *of `alkgit`*
  are gone (ADR-025's relocation: composition base + engine twin
  features, both now on `alkgit-substrate`); the `gix` engine feature
  is dissolved (ADR-021 §1, carried): gix component crates are
  ordinary dependencies of both crates (the pack pipeline, hash, wire
  toolkit); no on-disk odb engine is shipped.
- The protocol core compiles and tests without any substrate
  dependency structurally (`cargo test -p alkgit`; no backend features
  exist to disable).
- Hash: `sha1` pinned (the compile-time-rejected invariant from
  `docs/research/gitoxide.md`); `sha256` passthrough feature on the
  core crate (OQ-05
  policy unchanged; alkblobs' `GitObjectSha256` registry byte is
  already registered — no renumbering later).
- Encodes the POC-2 prerequisites by construction: handle sharing
  (the substrate `Find` front is constructed once, per-session views
  over it), generation on blocking threads, O(counts) memory,
  missing-objects abort; ingestion per ADR-020 §2
  (unpack-to-loose, pin bridge, owned connectivity walk).
- The published-crates rule: `alkblobs 0.2`, the alkstore family 0.1,
  alkcall 0.10, and `alkgit` itself (in `alkgit-substrate`'s manifest)
  pinned by version — never path deps to sibling
  checkouts (the gitoxide reading-clone rule applies).

## Concurrency model

- Implementations own their internal threading: the substrate impls run
  pack generation/ingestion on `spawn_blocking` with owned handles
  moved in (POC-2's shape, unchanged in effect). The **sync Find /
  async store bridge** is a two-context rule the impls own in one
  place: `Handle::block_on` from the blocking pipeline task; never
  from async contexts (`block_in_place` there — POC-O learned the
  panic the live way).
- `parking_lot` short-held locks; poisoned-lock recovery
  `unwrap_or_else(|e| e.into_inner())` (convention 2). No panics in
  library code; no `unwrap`/`expect` outside tests.
- **Trait execution model** (review 001 A-6): all five traits are
  `#[async_trait]` — `Send + Sync`, dyn-compatible (E0038 forbids bare
  `async fn`), signatures per ADR-012 §1. `GitRegistry::resolve` is
  async (one call per session/request, before the first protocol byte —
  ADR-011 §6); DQ-6 resolved: new traits follow the same posture
  (ADR-019 §9). alkstore's hand-boxed `BoxedFuture` posture stays
  confined to alkstore's surface.
- The wire layer enforces the pipeline-concurrency budget itself
  (ADR-009's permit acquired around each `GitPackGen`/`GitPackIngest`
  call); the impls' `spawn_blocking` is invisible from the wire layer.

## Public API surface

Per-crate crate-root re-exports (the alktty pattern; ADR-025's
partition):

- **`alkgit`**: backend traits + types (including `RepoRecord`,
  `AccessAction`, the `authorize` policy function, the authorized-repo
  marker type (the door constructs it only
  after the ADR-007 check passes — session tuples carry it,
  transport.md session substrate layer), and the `git/repo/*` op spec+handler
  pairs), `GitAdapter`/`register_openable` (producer), `GitSession`
  (consumer — the ADR-017 typed client: `ls_refs`/`fetch`/`push`),
  session substrate types (ADR-005 — including the service selector
  and the authorized-repo marker), `Limits`, protocol error enums;
  the `sha256` feature exports its hash-policy surface.
- **`alkgit-substrate`**: the composition family (substrate.md §public
  API is the member list) — the registry adapter trait pair + engine
  constructors under `sqlite`/`postgres`, the in-memory default
  adapter, the cached read provider, the reactor
  (announce/wake-runner/stream-consumer/reconcile), **the five
  backend-trait implementations** (the object-side trio included —
  the assembler constructs the backend from them), the substrate
  `Find`/odb front, the GC liveness sources, and the LFS surface
  under `lfs`.

The embedder-facing freeze point remains OQ-03 (per-crate after
ADR-025's re-partition).

## The two op kinds (ADR-012 §3, the classification)

alkgit ships both alkcall op kinds from one crate — the first family
payload to do so (alktty/alktunnels: open ops only; alknet-docker,
planned: call ops only):

- **Open op** (`channels/git/sub` via `register_openable`; open-op
  params `{repo, service}` — ADR-016) — the binary half: negotiation +
  service selector + ACL point (ADR-007; the service selects the
  `authorize` action, read for fetch / write for push), yields the
  duplex git session.
- **Call ops** (`git/repo/{create,delete,update,get}`) — the JSON half:
  thin `OperationSpec`+handler pairs over `Arc<dyn GitRegistryStore>`,
  `Visibility::External`, always-on (no substrate needed — the trait
  pair is the seam). `create` is gated by the static registry check
  (`required_scopes: ["git:repo:create"]`); `delete`/`update`/`get`
  carry an empty `AccessControl` and the handler evaluates the ADR-015
  gate — scope `git:admin` OR the record's `manage` grant, via the
  `authorize` policy function, generic FORBIDDEN denial, unknown-repo ≡
  unauthorized (ADR-008). Registered by the assembler wherever it wants
  them exposed. Managing records (op scopes) and git access (repo
  grants) are distinct sources — but the record is the single per-repo
  authz surface, and repo creation seeds the creator's
  `{read, write, manage}` grants (ADR-015).
- **Deletion is a GC consequence** (ADR-019 §6): `delete` removes the
  registry row; the refs rows are owned by the same removal; the repo's
  objects become liveness-uncovered and the substrate sweep reclaims
  them on its next pass. No synchronous object deletion exists.

## Registry types and schemas (review 001 N-3; the freeze-inventory draft)

The serde shapes the ops handlers and the registry adapters both
serialize — pinned here so the two workstreams cannot invent them
independently. These are the N-3 schemas ADR-015 chained after the
manage-grant shape: they capture the three-action grant shape (ADR-015),
the opaque grant-key rule (ADR-015 §7 / OQ-16), and the
unknown-repo ≡ unauthorized rule at the op layer (ADR-008). Draft until
first publish (additive fields have been added before publish before —
ADR-016's params, ADR-017's API); at publish they enter OQ-03's
inventory as compat surface. **Schema change this cycle: the
`storage_root` field is gone** (ADR-019 §8 — substrate-only storage
owns no path; the record is identity/visibility/grants only; ADR-008's
never-a-path rule holds a fortiori).

### `RepoRecord` (serde shape)

```json
{
  "repo_id": "alkdev/alkgit",          // the registry id (string, non-empty)
  "visibility": "public",              // "public" | "private" (lowercase)
  "grants": {                          // identity_id (opaque string, ADR-015 §7) → grant set
    "u-7731": ["read", "write", "manage"]
  }
}
```

- `grants` values are arrays of the action strings `read` / `write` /
  `manage` (unique, lowercase; `manage` implies the lower actions per
  ADR-015 §2's monotonicity — stored sets need not be closed, the
  policy function evaluates that).
- No path fields of any kind: the record never carries server-side
  storage locations (ADR-019 §8). Storage placement is the composition's
  constructor concern, keyed on `repo_id`.
- Additive-field rule: unknown fields are rejected, fail-closed, in op
  input schemas (`additionalProperties: false` — the same extension
  rule as ADR-016's open-op params; extensions replace, not accumulate,
  before publish).

### `RegistryError` (the registry-family error set)

The error type of traits 1–2 (`GitRegistry`/`GitRegistryStore`) —
the scope is the registry family, not the whole trait family (the
object-storage traits carry `StorageError`, ADR-018 §7, after the
"every failure the trait family can produce" claim was found to
over-reach — review 002 R-2). Five variants — every failure the
registry family can produce, no catch-all:

| Variant | Meaning | Wire mapping |
|---|---|---|
| `NotFound` | repo id unresolved | collapses with `Forbidden` to the generic denial (unknown ≡ unauthorized, ADR-008; transport.md error taxonomy) |
| `Forbidden` | gate denied (op-layer or `authorize`) | generic FORBIDDEN — same wire shape as `NotFound`, never a variant leak |
| `AlreadyExists` | create with a taken id | op-level error; still NOT disclosed as an existence oracle to non-creators — `create` responses may carry it, `resolve` never returns it |
| `Invalid` | malformed record/field (store-side validation, e.g. empty repo id, unknown action string) | op-level error with the field name |
| `Io(String)` | backing-store failure (serialized as message — not `std::io::Error`, which is not serde-stable) | session/op error, substrate-appropriate |

`Io` is the only stringly-typed variant; everything else is structural
so ops can report precise JSON errors
(`{"error": "already_exists", …}`) while the trait surface stays
`Result<_, RegistryError>` per ADR-012 §1.

`StorageError` (ADR-018 §7) is the object-storage sibling — traits 3–5:
`ObjectMissing { oid }` (the ADR-004 generation abort), `Invalid(String)`
(malformed/backend input), `Io(String)` (same stability rule as
`RegistryError::Io`). CAS-stale and fsck outcomes are deliberately
*not* variants: they are `RefOutcome`/`PreparedPush.unpack` values
(protocol reports the session survives), which is what keeps the
`no-catch-all` claim true for both error types.

### `git/repo/*` op JSON schemas (request/response)

| Op | Request | Response | Errors |
|---|---|---|---|
| `git/repo/create` | `{repo_id, visibility}` — no placement fields (ADR-019 §8; the substrate composition keys storage on `repo_id`); grants seeded `{read, write, manage}` for the caller (ADR-015 §4); an explicit grants field is an admin-shape extension, NOT v1 | `{repo_id, visibility}` | `already_exists`, `invalid` |
| `git/repo/delete` | `{repo_id}` | `{}` | `unauthorized` (the single wire code for unknown-repo ≡ not-authorized — N-2's collapse rule; `io`) |
| `git/repo/update` | `{repo_id, visibility?, grants?}` — both optional; PATCH semantics (omitted fields unchanged; present fields replaced wholesale — see the update-semantics note below); grant mutation is the only grant-write path (ADR-015 §3) | `{repo_id, visibility, grants}` (post-write record) | `unauthorized` (collapsed, N-2), `invalid`, `io` |
| `git/repo/get` | `{repo_id}` (single read; list is a v2 additive op) | `{repo_id, visibility, grants}` (no placement fields — ADR-019 §8) | `unauthorized` (collapsed, N-2), `io` |

- Error codes are the wire `code` strings in the table (snake_case,
  one namespace `git:repo:*`), each carried in the op's
  `ErrorDefinition` (alkcall ADR-016 disclosure convention — the same
  pattern alktty's `channel:open_failed` uses). The `unauthorized` code
  is the collapsed denial: both `RegistryError::NotFound` and
  `Forbidden` map to it at the op-wire boundary (unknown ≡
  unauthorized, ADR-008 + review 001 N-2), so no variant leaks an
  existence oracle; `AlreadyExists` is create-side only (the caller
  knows the id — discloseable without an oracle).
- **Update semantics are PATCH** (review 002 R-3 — the
  "full-record replace of the provided fields" qualifier was
  parseable either way, and the wrong reading is destructive:
  a visibility-only update clearing grants locks the grant-holder
  out): omitted fields are left unchanged, present fields are
  replaced wholesale (`grants`, when present, replaces the whole
  grant map — partial grant edits are read-modify-write at the
  caller). The response echoes the post-write record so the caller
  confirms what landed, including what was left unchanged.
- **`already_exists` disclosure posture** (review 002 R-6, recorded so
  a later agent does not "fix" it into an upstream-incompatible
  denial): a `git:repo:create`-scoped identity probing arbitrary ids
  *can* learn which exist — that is accepted. Create is a
  trusted, scope-gated, low-population surface; upstream gitea
  behaves the same; the position is that id-probing at create-scope
  is inside the trust boundary the scope already grants, while
  resolve-side disclosure (the fetch/push path) stays collapsed per
  ADR-008/N-2.
- `update`'s response echoes the post-write record so grant edits
  confirm what landed (the last-writer-race posture is ADR-012
  §Consequences, unchanged).
- All four op input schemas: `additionalProperties: false` (the
  alksocks `{}`-precedent — same fail-closed extension rule as
  ADR-016's open-op params).

### Repo-id grammar (review 002 R-5)

`repo_id` is the registry key on every wire and op surface, so its
grammar is pinned once here:

- **Shape**: one or two path segments, `owner/name` — lowercase
  alphanumerics and `-`/`_`, segments non-empty and at most 100 bytes
  total (the `owner/name` convention matches self-hosted git and the
  OQ-16 namespacing direction; a single segment is a valid legacy/
  local shape).
- **Wire handling** (ADR-008 governs): the id is an opaque registry
  key — parsed against this grammar for *rejection* only, never
  decomposed (the `owner` segment confers nothing; the registry index
  is flat), never joined/normalized into paths.
- **Error mapping**: a grammar-invalid id is `unauthorized` on the
  serving paths (collapsed with unknown-repo, ADR-008 — the id shape
  leaks nothing) and `invalid` on the op paths (create/update — the
  caller needs the correction).
- **Http mounting note** (for doors.md consumers): two-segment ids
  need a two-placeholder route (`/{owner}/{repo}/info/refs` with
  single-segment ids allowed too) — the door crate pins its own route
  grammar when the alkhttp `git`/`lfs` features land; alkgit's contract
  is only "the full id string arrives resolution-ready." Route tables
  on alkhttp door surfaces are static shapes (no route fallback — the
  POC-L/alkhttp constraint), so multi-segment ids mean enumerated
  patterns.

## 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 (record carries no path — ADR-019 §8) |
| [009]decisions/009-bounded-resources-budget.md | Budgets | limits flow into gen/ingest |
| [010]decisions/010-pure-protocol-crate.md | Pure protocol crate | producer/consumer halves, no doors/binary; granularity superseded (ADR-025) |
| [011]decisions/011-per-repo-authorization.md | Per-repo authorization | grants in records, policy in core (amended: ADR-015) |
| [012]decisions/012-registry-backing-and-ops.md | Registry + ops | write trait, CRUD ops, feature split (backing default superseded by ADR-019; gate amended: ADR-015) |
| [015]decisions/015-manage-grant-and-op-gate.md | Manage grant + op gate | `manage` tier, admin-scope-OR-manage gate, create seeds manage |
| [013]decisions/013-receive-pack-state-machine.md | receive-pack | thin-pack ingestion, transaction CAS, report framing (amended to the substrate shapes: ADR-020) |
| [014]decisions/014-v2-negotiation-ack-loop.md | Negotiation | ack loop, `common_haves` seam, no `ready` |
| [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 (construction shape simplified: ADR-023 §2) |
| [018]decisions/018-backend-trait-signatures-and-storage-error-model.md | Trait signatures + storage errors | signatures held with zero drift; `StorageError` for traits 3–5 |
| [019]decisions/019-substrate-baseline.md | Substrate re-baseline | storage on alkstore + alkblobs; `registry-file` scrapped; reactive-by-default; coherence contract |
| [020]decisions/020-receive-pack-duplex-amendments.md | receive-pack duplex | packless delete pushes (bounded peek); unpack-to-loose ingestion; pin bridge |
| [021]decisions/021-feature-graph-and-dependency-posture.md | Feature graph | **superseded by ADR-025** (graph re-partitioned) |
| [025]decisions/025-two-crate-workspace.md | Two-crate workspace | crate + feature model (the placement table), the seam as the inter-crate contract |
| [022]decisions/022-lfs.md | LFS | media under `GitObjectSha256`; alkhttp `lfs` door; wired liveness |
| [023]decisions/023-replication-v1.md | Replication v1 | protocol-native via `GitSession`; mirror plane = optimization bucket |

## Open Questions

- **OQ-03**: publish freeze inventory (the op set enters it; the
  revised `RepoRecord` schema enters it).
- **OQ-05**: sha256 policy (deferred(scope)).
- **OQ-17**: relational registry schema migrations — resolved as
  ADR-024 (construction-run, version-stamped, additive-first; the LFS
  refcount column ships in the initial schema).
- **OQ-19**: implementation-phase wire validations (new — the
  unborn-HEAD rider and the `common_haves` substrate-odb validation).
- OQ-06 resolved by ADR-012 and now *superseded by ADR-019* (registry
  backing is the substrate composition; `registry-file` is scrapped).
- OQ-04 resolved (ADR-013, amended: ADR-020); OQ-02 resolved (ADR-014).

## References

- [substrate.md]substrate.md (the shipped composition this seam
  fronts — reactive halves, coherence contract, GC chain, adapters)
- [lfs.md]lfs.md (the LFS surface)
- `docs/research/poc2-findings.md` (generation pipeline +
  prerequisites), `docs/research/poco-findings.md`,
  `docs/research/pocr-findings.md`, `docs/research/pocx-findings.md`
  (the re-baseline evidence)
- `docs/research/substrate-recomposition.md` (the re-baseline doc)
- `docs/research/gitoxide.md` (API contract notes — normative for the
  gix component usage)
- alktty `backend.rs` (the trait template)
- alknet ADR-033/035 (the repo/adapter + read/write-split pattern)
- ADR-010 (the structural decision), ADR-025 (the crate model this
  doc's feature section implements), ADR-011/012/018 (the seam this
  doc specifies), ADR-019..023 (the re-baseline round)