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
//! Answering `want`/`have` from **the store's own** reachability index.
//!
//! [`crate::bitmap`] is this module's sibling and they answer the same question
//! for two different kinds of store. The difference is where the set algebra
//! happens:
//!
//! ```text
//! crate::bitmap store hands out BITS → this crate does the algebra
//! (git's EWAH over one pack's positions, plus the .idx
//! that gives those positions names — so the caller can
//! resolve them and a bounded walk can add to them)
//!
//! this module store hands out the ANSWER
//! (roaring over the store's own ordinals; only the store
//! can resolve an ordinal, so only the store can do the
//! algebra)
//! ```
//!
//! # Why the second shape exists at all
//!
//! gunnar's znippy cold tier has no `.pack` on disk and no `.idx`, by design:
//! the storage contract replaced them with an Arrow objects table, and the end-to-end proof of
//! that store asserts those files do **not** exist. Its reachability is real,
//! exact and already built — roaring bitmaps over object ordinals — but it is
//! not addressed by pack position and cannot be, because there are no pack
//! positions.
//!
//! Encoding it into git's format to satisfy a `.bitmap`-shaped accessor's
//! return type was the first thing tried, and it is a **tax with no payer**:
//! serialise EWAH over synthetic positions so that [`crate::bitmap`] can decode
//! it back into the set algebra the store had already done. It also needs three
//! facts the store does not have — a resolved object kind per object (the objects
//! table stores
//! the *pack entry* type, so a delta's is `OfsDelta`), per-commit stored bitmaps
//! (`GitOps` exposes a query, not the table), and a single pack position space
//! (the store holds N concatenated verbatim packs). The seam was widened
//! instead of the answer being bent — and it is `GitServe::select` now, on the
//! contract itself rather than on a gunnar-owned trait above it.
//!
//! # What this module actually does
//!
//! Very little, and that is the point. Steps 1, 2, 8 and 9 below are lifted
//! from [`crate::bitmap::select`] because they are the same steps and must
//! stay the same (one implementation — the difference is one call, not a
//! second selection algorithm). Everything between them is
//! [`GitServe::select`](gunnar_store::git_storage_trait::GitServe::select).
//!
//! # What it declines
//!
//! Rather than approximate — the same list [`crate::bitmap`] keeps, for the same
//! reason, plus one more:
//!
//! * a **partial-clone filter**, a **shallow boundary**, a **deepening**'s
//! `restrict` set: membership decided per object by facts no reachability
//! index carries;
//! * a **tip that does not peel to a commit**. `GitServe::select`'s contract
//! is commits in, closure out; a `want` naming a tree or a blob
//! outright — a partial clone's lazy fetch does exactly this — needs a tree
//! closure the index was never asked for. [`crate::bitmap`] can serve that
//! case because it can *add* to a bit set; this one cannot add to an answer.
//!
//! Every one of them falls through to `select_objects`' walk and is served
//! exactly as it was before this module existed.
//!
//! # What it does **not** decline, and why that is deliberate
//!
//! The **object ceiling**. `Limits::object_bytes` is `Some` by default, so
//! declining on it would turn this path off on every serve path there is while
//! every test stayed green — which is precisely what happened to
//! [`crate::bitmap`] on 2026-08-04, four end-to-end cases against stock git
//! reporting green while measuring the walk. So it is enforced here, in
//! [`enforce_object_ceiling`], and [`crate::graph::Selection::answered_from_bitmap`]
//! is set so a guard can assert which path answered rather than infer it.
use ObjectId;
use object;
use crate;
use crate;
/// `select_objects`, answered by the store.
///
/// `Ok(None)` means "this request is not one this index can answer exactly";
/// the caller walks. See the module documentation for the list.
pub
/// `Limits::object_bytes`, applied to a selection that was decided without ever
/// opening an object.
///
/// # Why every object and not only the blobs
///
/// [`crate::bitmap::enforce_object_ceiling`]'s equivalent asks the pack's four
/// type indexes which positions are blobs and pays a header read only for
/// those. There is no type index here:
/// [`ReachSet`](gunnar_store::git_storage_trait::ReachSet) names the
/// commits, because the store's commit graph is what built the bitmaps, and
/// says nothing about the rest. So the commits are skipped and everything else
/// gets a header read whose `kind` decides whether the size matters — the same
/// `known_blob: false` branch the bitmap path uses for its extended index, and
/// the same reason: the type is not known, so the header answers it.
///
/// A header read is not an object read. Every backend that reaches this path
/// answers `header` without inflating a payload.
/// # The bulk pre-pass, and why it is exactly equivalent
///
/// The loop below asks one thing per object: *is it over the limit?* An object
/// **under** the limit passes whatever its kind is — the `kind != Blob` arm only
/// ever turns a would-be refusal into a pass. So a size alone decides every
/// object under the ceiling, and the kind is needed only for the ones over it.
///
/// That matters because `header` is cheap on a packfile backend and expensive on
/// an archive-backed one, where it is a single-oid index lookup plus a walk of
/// the object's whole delta chain plus a partial inflate. MEASURED on the `h2h`
/// fixture: **34 124** of those per clone, to establish that a repository whose
/// largest blob is 16 KiB holds nothing over the 1 GiB default. Not one could
/// return true.
///
/// [`GitServe::sizes`](gunnar_store::git_storage_trait::GitServe::sizes) answers
/// all of them in one batched index pass when the backend has a column for it.
/// `None` for an individual row — unknown, not "fine" — falls through to the
/// `header` arm exactly as before.
///
/// **An unknown size falls through to `header`; it is never a pass.** A store
/// one drain behind reports a just-pushed object absent, and treating that as
/// "under the limit" would skip the ceiling for precisely the objects a push had
/// introduced. One writer decides the outcome — the loop below — and the bulk
/// read only ever spares it work (one implementation).
/// The znippy arm's words when its absorb gate is held — a pack is being
/// indexed and its reachability rows do not exist yet
/// (`znippy-plugin-git` `GitStore::index_in_progress`).
pub const INDEX_IN_PROGRESS: &str = "index in progress for this repository";
/// Is `err` the store saying its index is being built (and nothing worse)?