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
---
status: reviewed
last_updated: 2026-10-23 (the wave-5 release gate — OQ-19 resolved;
  the core-side OQ-03 inventory settled)
---

# Open Questions

All unresolved architecture questions, centrally tracked. Status values:
`open` (needs resolution now), `partially resolved` (decision made but a
narrower question remains — named in the entry), `resolved` (decision
made, ADR recorded), `deferred(scope)` (waiting on external information
*or* on a not-yet-existing internal artifact — a booked work item —
the blocked-on condition states which), `deferred(unclear)` (pieces
exist, shape needs investigation). See `docs/sdd_process.md` for the
deferral protocol (blocker tasks in `tasks/architecture/`).

**Door type** classifies reversal cost: `one-way` decisions are
expensive/impossible to reverse once published (wire formats, public API
shapes); `two-way` decisions can be revisited while nothing is published.
Door type does not change urgency — all decisions here need resolution
when their impacts say so; it records how careful the resolution must be.

## Deferred / Blocked summary

| OQ | Status | Blocked on / investigation |
|---|---|---|
| OQ-05 | deferred(scope) | ecosystem need for sha256 |
| OQ-03 | partially resolved | first-publish timing (release decision; **the core-side freeze inventory settled at the wave-5 gate, 2026-10-23** — enumerated in the gate record; the substrate-side inventory settles at the substrate crate's publish) |
| OQ-16 | deferred(scope) | first cross-assembly record sharing (distributed-git phase; tracker `architecture/oq-16-grant-identity-namespace`) |
| OQ-17 | resolved (ADR-024) | — |
| OQ-19 | **resolved (2026-10-23)** | — (all three riders served/dispositioned at the wave-5 gate, or recorded honestly) |
| OQ-20 | deferred(scope) | per-oid pruning of stale LFS reference rows — first concrete row-volume need (repo-lifetime scoping is correct v1) |
| OQ-21 | deferred(scope) | build: first client needing the LFS lock server (`lfs.locksverify false` is the verified workaround) — *plus* the composition-validation POC owed before the v0.1 publish |

OQ-02 and OQ-04 resolved this cycle (ADR-014, ADR-013; §3/§6 amended by
ADR-020); OQ-08/07/09/01 resolved in earlier cycles; **OQ-19 resolved
2026-10-23 at the wave-5 review gate** (all three riders served; none
retracted). OQ-06 (resolved by
ADR-012: `registry-file` default) is **superseded by ADR-019** — the
registry backing is now the substrate composition and `registry-file`
is scrapped. The substrate re-baseline cycle (ADR-019..023, 2026-10-19)
resolved the recomposition DQ ledger: DQ-1/2/3/4/6/8 in ADR-019,
the receive-pack wire facts in ADR-020, DQ-5 in ADR-022, the
replication posture in ADR-023; DQ-7's co-tenancy/connection facts
resolved as *assembly documentation* (ADR-019's negative
consequences, substrate.md §assembly shapes — measured data, tuning
knobs, and the connection budget, not an ADR-shaped decision). The
2026-10-20 crate-shape round (ADR-025, relitigating OQ-09's
granularity sub-decision) made alkgit a two-crate workspace — protocol
core + substrate composition; OQ-03's freeze inventory re-partitions
per crate. OQ-16 is deferred but blocks nothing in
v1 (grants key on cluster-local ids within one assembly).

## 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). *(Amended 2026-10-20: the
  crate-granularity sub-decision within that resolution was superseded
  by ADR-025 — alkgit is now a two-crate workspace; the protocol-core
  framing and everything else in the resolution stands.)*
- **Resolution**: [decisions/010-pure-protocol-crate.md] the `gix`
  engine feature later dissolved — ADR-021 §1; the impls are the
  substrate crate — ADR-025; granularity superseded by
  [decisions/025-two-crate-workspace.md]. The full
  sub-decision set recorded there (ssh deletion, http home, crate
  granularity, backend-trait surface, binary fate); ADR-025 owns crate
  granularity hereafter.
- **Cross-references**: ADR-001/006 (superseded), ADR-025, 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**: 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 alkgit" means concretely)

- **Origin**: [overview.md], [transport.md], vision §"ALPN as a service"
- **Status**: **partially resolved** — the shape is settled (ADR-010
  as amended by ADR-019..021; its granularity sub-decision and
  ADR-021's graph superseded by ADR-025): embedding = the
  protocol core `alkgit` + the five backend traits (own storage via
  `alkgit` alone, or the shipped composition via `alkgit-substrate`
  with its feature graph: `sqlite`/`postgres`/`lfs`) + optional door
  features. ADR-025 (2026-10-20) re-partitioned the freeze inventory
  across the two crates: the core's surface (the five traits, seam
  types, `GitSession`, producer half, the `git/repo/*` op set, the
  preamble shapes, `Limits`, errors) freezes at the core's first
  publish; the composition's member list
  (substrate.md §public API — fixes the members, not the identifiers)
  freezes at the substrate crate's first publish. The *identifiers* on
  both sides remain deferred to the publish-time freeze. What ADR-025
  changed was the inventory's crate assignment, not its members. The
  prior single-crate pinning history: ADR-012 added the `git/repo/*` op
  set (names + schemas) to the inventory; ADR-013/014 added the
  push/negotiation trait surface (`GitPackIngest` binding,
  `GitPackGen::common_haves`); ADR-016 added the native-path preamble
  wire shapes (the `{repo, service}` open-op params schema and the
  git-daemon request-line grammar — the last native-path wire surface);
  ADR-017 added the `GitSession` public API; the registry types/schemas
  draft (backend.md §"Registry types and schemas", review 001 N-3)
  pinned `RepoRecord`, `RegistryError`, and the `git/repo/*` op
  schemas; ADR-018 pinned the object-storage trait signatures, the
  shared backend-seam types (`RefLine`/`RefUpdate`/`RefOutcome`/
  `PreparedPush`/`PushOptions`), and `StorageError` — the backend-seam
  inventory is complete. The substrate round's additions: the
  **revised `RepoRecord` schema** (no `storage_root` — ADR-019 §8) and
  the substrate composition's public members (now
  `alkgit-substrate`'s freeze set).
- **Door type**: one-way (API freeze is registry-visible to dependents)
- **Priority**: medium
- **Impacts**: blocks the first publish only, not implementation. The
  re-partition also un-blocks the core's freeze from the substrate-side
  validations (OQ-21's composition POC gates the substrate crate's
  publish, not the core's — the core's freeze waits only on OQ-19's
  wire validations against the real clients, which exercise the core).
- **Blocked on**: per crate — the *core's* wire-validation blocker is
  **cleared** (OQ-19 solved 2026-10-23: all three riders served or
  dispositioned; the core-side freeze inventory settled at the wave-5
  gate — `tasks/implementation/core/review-wave-5.md` carries the
  enumeration); the core's publish now waits only on release timing
  (`core/w5-publish`). The *substrate  crate's* publish still waits on
  OQ-21's composition-validation POC. Both
  additionally wait on first-publish timing (a release decision, not
  an architecture question).
  The API surface inventory lives in
  [backend.md]backend.md §public API, [transport.md]transport.md
  §public API, and [substrate.md]substrate.md §public API.
- **Cross-references**: ADR-010, ADR-025, ADR-002, ADR-012, ADR-013,
  ADR-014, ADR-016, ADR-018, ADR-019, ADR-021 (superseded), backend.md,
  transport.md, substrate.md

## Theme: transport / protocol

### OQ-02: V2 multi-round negotiation (ack/NAK logic, `wait-for-done` retirement)

- **Origin**: [transport.md], poc2-findings §"does NOT settle"
- **Status**: **resolved** — ADR-014 (V2 negotiation ack loop). The
  duplex-capture walkthrough against git 2.43.0 (ground-truth negotiation
  mock, cross-checked against `fetch-pack.c`) resolved the grammar: no-
  `done` rounds get `acknowledgments` (`ACK <oid>` per recognized have,
  `NAK` when none, flush — never `ready`, so FLUSH is always the
  terminator); the `done` round generates closure(wants) − closure(haves)
  with no cross-round server state (clients re-send wants + commons every
  round); `wait-for-done` stays and the advertisement text is unchanged;
  the ack check is a new backend-trait method (`common_haves`) keeping
  the honest boundary at the seam. Captures:
  `docs/research/negotiation-captures.md`.
- **Resolution**: [decisions/014-v2-negotiation-ack-loop.md]
- **Cross-references**: ADR-003, ADR-004, ADR-005, ADR-009, ADR-014,
  transport.md §fetch, backend.md §"The trait family" (GitPackGen)

### OQ-04: receive-pack (push) — validation gap

- **Origin**: [transport.md], poc3-findings §"does NOT settle"
- **Status**: **resolved** — ADR-013 (receive-pack state machine). The
  walkthrough against real `git push` captures (git 2.43.0: file://,
  git://, smart-http, plus raw stdio requests into real `git receive-pack`)
  resolved every listed unknown: the push path is V0-framed by upstream
  design (no version negotiation, ADR-003 governs fetch only); the
  advertisement is a V0-shaped ref advertisement (caps on the first ref
  line, `capabilities^{}` sentinel only for empty repos) with served set
  `report-status(-v2) delete-refs side-band-64k atomic ofs-delta
  object-format=sha1` (+ `push-options` config-gated); shallow request
  lines are rejected up front for v1 (symmetric with fetch); thin packs
  accepted with bases from the server odb (default client behavior, no
  capability); ingestion =
  `Bundle::write_to_directory_eagerly` +
  `gix-fsck` + one `gix-ref` transaction per push (`.keep`-guarded pack
  landing) — *(the ingestion composition and the pack-arrival shape
  are amended by ADR-020 on the substrate baseline: unpack-to-loose
  under the pin bridge, the bounded packless-push peek)*; CAS timing is unpack-first-then-per-ref (observed upstream
  order); status report is band-1 pkt-line-framed with inner flush when
  sideband was selected. Captures:
  `docs/research/push-captures.md`.
- **Resolution**: [decisions/013-receive-pack-state-machine.md]
  (amended by [decisions/020-receive-pack-duplex-amendments.md])
- **Cross-references**: ADR-003, ADR-004, ADR-005, ADR-009, ADR-013,
  ADR-020, transport.md §receive-pack, backend.md §"The trait family"
  (GitRefs, GitPackIngest), doors.md, substrate.md

### OQ-05: sha256 support policy

- **Origin**: [transport.md], git-protocol.md §"Open items"
- **Status**: deferred(scope)
- **Door type**: two-way
- **Priority**: low
- **Impacts**: none for v1 (sha1 pinned); feature-flag passthrough
  compiles but is untested end-to-end.
- **Blocked on**: ecosystem need (a real client/repo requiring sha256) or
  upstream gix sha256 maturity; POC-2 left the pipeline hash-generic but
  untested. Tracker task: `tasks/architecture/oq-05-sha256.md`.
- **Cross-references**: ADR-003, ADR-004

## Theme: identity / auth

### OQ-08: Registry identity space + vault placement (narrowed)

- **Origin**: [overview.md], [doors.md], [backend.md]; originally "identity
  sources per front door"
- **Status**: **resolved** — ADR-011 (per-repo authorization): the
  identity model is alkcall's resolved `Identity` keyed on its stable
  logical id (alkcall ADR-025 property, referenced); alkgit stores no
  identity records; grants live in repo records; the public+read /
  always-authenticated-write policy is alkgit-core's `authorize`
  function (the clause ADR-007 pinned but the static ACL engine could
  not express); vault placement resolved as **nothing to place in v1**
  (no credential-shaped material in metadata).
  *(Grant-key id format declared deliberately open by ADR-015 §7 — the
  cross-identity-namespace question this leaves is tracked as OQ-16.)*
- **Resolution**: [decisions/011-per-repo-authorization.md]
- **Cross-references**: ADR-007, ADR-008, ADR-011, ADR-012, OQ-06, OQ-07,
  backend.md §GitRegistry, doors.md

### OQ-16: Grant-key identity namespace (globally-comparable ids for multi-hub grants)

- **Origin**: ADR-015 §7; user session (2026-09-26)
- **Status**: deferred(scope)
- **Door type**: two-way (schema field format; grants are already opaque
  strings, so this constrains only the *contents*, not the alkgit API)
- **Priority**: low (blocks nothing in v1 — grants key on alkcall's
  cluster-local id within one assembly, which is the only deployment
  v1 serves)
- **Question**: what identity-id format do grants key on when repo
  records are shared across assemblies (the distributed/replicator
  deployment: contract-watching hubs mirroring grant state)? Today the
  id is alkcall's `Identity.id` — stable within one cluster, but
  meaningless across hubs. Candidate shapes: `{namespace}/{id}`
  compound ids (the `{user|org}/{repo}` convention familiar from
  self-hosted git — e.g. `alkdev/alkgit` locally, `alkimiadev/alkgit`
  when mirrored — extended to identity: `gh:alkimiadev`,
  `alk:alkdev`), did-key / contract-derived identifiers (the planned
  smart-contract org/user/repo model), or a two-part
  (authority, local-id) tuple.
- **Constraints any resolution must satisfy** (recorded from ADR-015 §7):
  grants remain flat sets keyed on opaque strings; alkgit never parses,
  validates, or interprets the id — collision-freedom is the
  identity-provider's guarantee; a downstream namespace can be
  introduced at the assembly seam without an alkgit change.
- **Impacts**: only the downstream identity-provider seam and the
  future platform app / replicator grant-sync format. The v1
  `authorize` policy function, the grant schema shape, and the op gate
  are all id-format-agnostic by construction.
- **Blocked on**: the first deployment that shares repo records across
  assemblies (the distributed-git phase; also touches alkcall's
  identity model if a family-wide id format is preferred over a
  payload-level namespace convention). Tracker task:
  `tasks/architecture/oq-16-grant-identity-namespace.md`.
- **Cross-references**: ADR-011 §1, ADR-015 §7, OQ-08

## Theme: storage / metadata

### OQ-06: Registry/metadata backing store (gix feature)

- **Origin**: [backend.md] was storage.md, ADR-008
- **Status**: **resolved then superseded** — ADR-012 (registry backing +
  write surface): the alknet repo/adapter pattern applied — `GitRegistry`
  read trait + `GitRegistryStore` write supertrait; default backing was
  the `registry-file` feature, persistence adapters future/additive.
  **Superseded 2026-10-19 by ADR-019** (the substrate re-baseline):
  the registry backing is now the co-tenant relational composition over
  the substrate engines behind the family adapter pattern (engine
  twins for sqlite/postgres + the in-memory default adapter), and
  `registry-file` is scrapped outright (no shim, no also-ran fallback —
  the family's scrap discipline). The read/write trait split itself
  survives verbatim (the ADR-018 signatures held with zero drift).
- **Resolution**: [decisions/012-registry-backing-and-ops.md] §1–2, §4
  (superseded) → [decisions/019-substrate-baseline.md] §3
- **Cross-references**: ADR-008, ADR-010 (granularity superseded by
  ADR-025), ADR-011, ADR-019, backend.md §crate and feature model,
  substrate.md

### OQ-17: Relational registry schema migrations (versioning story for the co-tenant tables)

- **Origin**: POC-R's "not settled" list; ADR-019 → substrate.md
  (the shipped composition's tables)
- **Status**: **resolved** — ADR-024 (2026-10-19): migrations are
  construction-run on the adapter's own connection, ordered monomorphic
  per-twin steps one transaction each, concurrent-open-safe via the
  alkstore race-verification pattern; one alkgit-owned stamp table
  versions the co-tenant schema as a unit (records + refs + seq + LFS
  rows); additive-first with the coexistence contract as its declared
  consequence and no refuse-gate while steps are additive (a
  non-additive step is major-version with its own ADR owning the
  refusal semantics); the LFS refcount column ships day-one in the
  initial schema (no migration owed); the in-memory adapter is
  stamp-free. No operator surface is owed (ADR-010).
- **Resolution**: [decisions/024-registry-schema-migrations.md]
- **Cross-references**: ADR-019 §3, ADR-022 §4, ADR-024, substrate.md,
  lfs.md, backend.md

### OQ-19: Implementation-phase wire validations (unborn-HEAD rider; `common_haves` over the substrate odb)

- **Origin**: [transport.md] §ls-refs (the N-5 rider);
  poco-findings §"what this does NOT settle" + pocx-findings (the
  NAK-only ack caveat)
- **Status**: **resolved** (2026-10-23, the wave-5 review gate): all
  three riders validated or dispositioned, none retracted —
  (a) the unborn-HEAD symref line served against a real client on both
  substrates (`git ls-remote` + an unborn-HEAD clone complete over the
  duplex git:// and stateless http doors, `tests/real_client.rs`); the
  capture **corrected the wire emission shape** to upstream's literal
  `unborn` token (a zero-oid HEAD makes a real client want the null
  oid and error the session — ADR-018's amendment note; the inbound
  parser accepted both shapes all along). (b) the positive-ACK capture
  landed machinery-side (`core/w5-oq19-positive-acks`) — validated on
  both substrates; **the substrate-odb facet re-runs in track B wave
  7** against the real Find before the substrate crate's freeze. (c)
  the real-client token inventory (`core/w5-oq19-real-client-tokens`):
  `include-tag` accepted + honored by omission (un-gated in git's V2
  send path, no `fetch=` slot to retract through), `session-id=`
  honestly retracted (never advertised → never sent), the rest
  declined fail-closed as unimplemented.
- **Door type**: two-way (capability tokens retractable until
  published) — *now frozen with the core's publish (OQ-03 core-side)*
- **Priority**: low (resolved; was neither blocking anything but the
  freeze)
- **Question**: two advertised/machinery promises lacked capture-level
  validation over the final substrate shape: (a) serving an unborn
  HEAD's symref line for `ls-refs=unborn` on both substrates with a
  real client (the capability token was client-accepted in POC-1; the
  *line* was never exercised; the unborn fixture is trivial now that
  refs are rows); (b) `common_haves` actually recognizing haves over
  the substrate Find in a multi-round ack loop (POC-X's fetch ran
  multi-round with NAK-only acks — the refinement is a negotiator
  optimization, not a correctness need: the trim rides haves-closure).
  (A third rider joined from the wave-2 gate: the negotiation
  machine's fail-closed declines of tokens real clients send.)
- **Impacts**: the advertisement text (drop the token if (a) fails)
  and the negotiation behavior's validation record. Not structural.
- **Resolution**: the wave-5 wire-validation sessions (the tracker
  task `tasks/architecture/oq-19-wire-validations.md` carries the
  outcomes per rider; OQ-03's core-side freeze inventory settled at
  the gate — `tasks/implementation/core/review-wave-5.md`).
- **Cross-references**: ADR-003, ADR-014, transport.md, backend.md,
  OQ-03

### OQ-07: Admin API operation set (v1 scope)

- **Origin**: [overview.md], alk-stack.md §"The gitea lesson"
- **Status**: **resolved** — ADR-012 (§3, §5): the crate ships a minimal
  CRUD op set (`git/repo/{create,delete,update,get}`) as thin call ops
  over `GitRegistryStore`, `Visibility::External`, always-on, gated by
  the global admin scope and per-repo grants. The old "Internal ops over
  an admin listener" framing is superseded (right mechanism, wrong axis
  — visible-surface = authorized-surface is served by the ACL). The
  gate table as written used scope+ownership and is replaced by ADR-015
  (the `manage` grant tier — admin scope OR manage grant, handler-
  evaluated; create seeds the creator's `{read, write, manage}` grants).
  Ops stay record-scoped; anything beyond repo records (users, orgs) is
  a downstream platform crate (the recorded split trigger, ADR-012 §5) —
  the app compiles roles/teams into flat grants (ADR-015 §6).
- **Resolution**: [decisions/012-registry-backing-and-ops.md] §3, §5
  (gate amended by [decisions/015-manage-grant-and-op-gate.md])
- **Cross-references**: ADR-007, ADR-011, ADR-012, ADR-015, alkcall
  ADR-017/011, backend.md §"The two op kinds"
## Theme: LFS

### OQ-20: Per-oid pruning of stale LFS reference rows

- **Origin**: [lfs.md] the pointer-lifecycle table's scoping note
- **Status**: deferred(scope) — the repo-lifetime scoping is correct v1
  behavior; per-oid pruning is a future shape awaiting a real need
  (the register's earlier `open` here was a classification slip: the
  blocked-on condition was stated all along, so this was never a
  resolve-now question)
- **Door type**: two-way (row policy; nothing published)
- **Priority**: low
- **Question**: the v1 row store scopes rows to the repo's lifetime
  (written at reference assertion; removed with the repo or by pointer
  removal). Rows for oids no longer referenced by any live ref in their
  repo (history rewritten, branch deleted) linger until repo deletion.
  Per-oid pruning — a row-reclamation op that drops rows whose
  pointers are no longer reachable (via the ref-graph walk alkgit
  already does for object liveness) — is the future pointer-lifecycle
  shape that reclaims them sooner. The sweep-window semantics make the
  cost of *not* having it bounded but nonzero (rows are tiny; the
  media may be re-protected while any row remains).
- **Impacts**: only the pointer-lifecycle ops' future surface and the
  LFS liveness source's precision.
- **Blocked on**: a concrete need (row volume) or the first LFS-heavy
  deployment; the repo-lifetime scoping is correct v1 behavior.
  Tracker task: `tasks/architecture/oq-20-lfs-row-pruning.md`.
- **Cross-references**: ADR-022 §4, lfs.md, OQ-17

### OQ-21: The LFS lock server (`lfs.locks`)

- **Origin**: [lfs.md] the constraint note, POC-L (unimplemented)
- **Status**: deferred(scope) — two layers, kept distinct: the *surface
  build* waits on a client need; a *composition validation* is owed
  before the v0.1 publish (the second layer is not a wait — see
  Investigation).
- **Door type**: two-way (additive surface)
- **Priority**: low for the build; medium for the validation (it gates
  the publish posture, not any v1 behavior)
- **Impacts**: none on v1 behavior — `lfs.locksverify false` is the
  client-verified posture and no lock capability is advertised. What
  the validation gates is the recorded feasibility posture: the specs
  assert "the routes and alkstore lock machinery (named TTL locks)
  exist and compose" — and the *compose* half is currently unevidenced
  (POC-L recorded the lock server as unimplemented). v0.1 should not
  go out sitting on a guessed posture behind the published substrate
  pair.
- **Investigation** (owed before first publish): a small POC walking
  the real `lfs.locks` API flows (create/list/verify/unlock) over the
  substrate stack — alkstore's named-TTL lock machinery against the
  API's actual semantics, the lock rows co-tenant on the engine the
  two substrate crates share (the sqlite single-writer contact point
  with alkblobs' machinery on the same file — contention behavior is
  part of what the POC measures), the door routing over the batch
  adapter. Both outcomes are wins taken before publish: the posture
  gets its evidence and the build deferral stands unchanged; or
  substrate gaps surface now, while they are cheap upstream asks
  rather than "real" problems discovered at this level.
- **Blocked on**: the build on the first deployment/client that needs
  the lock API (contention control for shared big-media files); the
  validation on its booked work item — it can run any time before
  the v0.1 publish (a not-yet-existing internal artifact, the
  deferred(scope) booked-item case) — a natural next slot alongside
  OQ-19's wire-validation sessions.
- **Cross-references**: lfs.md, doors.md, OQ-03 (the publish gate),
  OQ-19 (the sibling owed-validation, same honesty rule)