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
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
//! The fixture matrix (T4.1, D-088).
//!
//! # Why this file exists
//!
//! Until it did, every performance claim in this project was measured on one
//! graph shape: [`Shape::StarOfStars`], the chain of stars `benches/budgets.rs`
//! seeds. That shape is a **tree**, and a tree has exactly one path to each
//! node, so any cost that grows with *path count* is identically flat on it.
//!
//! Three defects came out of that single frame:
//!
//! * [D-070] explained `load_subgraph`'s superlinearity as the `DISTINCT` sort,
//! rigorously and wrongly — on a tree the walk emits 1,011 rows for 1,011
//! nodes, and the term that dominates on a real graph is 1. [D-076] measured
//! 299,593 walk rows for 49 nodes on a layered graph.
//! * T0.1's path-enumeration cost was invisible for the same reason.
//! * [D-059] left an open note that the chunk constants are empty-database
//! figures "and need a realistic fixture, which requires deciding what
//! 'realistic' means."
//!
//! This file decides what realistic means: **four shapes, each named for the
//! cost it is the worst case for**, so that a measurement taken on one of them
//! is a measurement of something stated rather than of whatever the fixture
//! happened to be.
//!
//! # The rule that goes with it
//!
//! A performance decision entry names the shape(s) it was measured on. D-070's
//! would have read *"inherent on `star_of_stars`"*, and the gap would have been
//! a sentence in the entry rather than a wave.
//!
//! # Why a path-included module rather than a feature-gated `src` module
//!
//! Fixture builders are dev-only code and do not belong in the shipped library,
//! and a `fixtures` feature would have to be listed in `required-features` on
//! `[[bench]]`, which makes a plain `cargo bench` skip the benchmarks silently.
//! `tests/common/harness.rs` already establishes the pattern; both live under
//! `tests/common/` because Cargo auto-targets every top-level `tests/*.rs` as
//! its own integration-test binary, and a shared module there compiles into an
//! extra binary with no tests in it.
//! benches and examples reach this file with
//! `#[path = "../tests/common/fixtures.rs"] mod fixtures;`.
//!
//! [D-059]: ../docs/architecture/s13-decision-register.md
//! [D-070]: ../docs/architecture/s13-decision-register.md
//! [D-076]: ../docs/architecture/s13-decision-register.md
// Every consumer uses a different subset: the benches want the seeders, the
// plan-pinning tests want the structural facts, `fixture_matrix_diag` wants
// both. Per-item `allow` would be noise on every item in the file.
use *;
pub const TS: &str = "2026-01-01T00:00:00.000000Z";
pub const OPEN: &str = "9999-12-31T23:59:59.999999Z";
/// The four shapes, and what each one is the worst case for.
///
/// Every shape is a pure function of `nodes`: no randomness, no clock, no
/// environment. A measurement that cannot be re-run to the same edge set is not
/// a measurement, and a fixture seeded from a hash of the machine name is how
/// that happens by accident.
pub const ALL_SHAPES: & = &;
// ---------------------------------------------------------------------------
// The shapes
// ---------------------------------------------------------------------------
//
// Doctrine constraint every one of them is written under: `links` permits at
// most one **open** interval per (source, target, edge_type), enforced by
// `trg_links_single_open`. So a shape may not emit the same ordered pair twice.
// Each builder below is written so that it structurally cannot — the pair set
// is a function of distinct (i, k) with a stated injection — and
// `fixtures_tests` checks it rather than trusting the argument.
/// A three-tier chain of stars: one root, its children, their children.
///
/// This is `benches/budgets.rs`'s `seed_edges` preserved exactly, down to the
/// thirds arithmetic, because the value of keeping it is that pre-0.6.0 figures
/// remain comparable. Changing it "slightly while moving it" would silently
/// invalidate the record this fixture exists to preserve.
/// Communities of [`CLUSTER_SIZE`], densely linked inside, one bridge out.
///
/// Dense-inside is what makes this the path-enumeration case: within a
/// community of `k` nodes there are many distinct routes between any two, so a
/// depth-3 walk enumerates paths rather than nodes, and the ratio of walk rows
/// to reachable nodes is the number D-076 found and D-070 could not see.
///
/// **Intra-community links go both ways**, which is not decoration. An
/// `i -> j, i < j` clique is a DAG: path length is bounded by the community
/// size and the walk stays close to linear, which measured 9× and would have
/// made this shape a weaker `star_of_stars` rather than a different question.
/// Both directions make each community strongly connected — what Louvain and
/// `scc` are looking for, and what makes the walk revisit — and take the same
/// depth-3 walk to ~40×. The ordered pairs are still distinct, so the
/// single-open trigger is satisfied.
pub const CLUSTER_SIZE: usize = 12;
/// A long path `0 -> 1 -> 2 -> …`, with a short spur every
/// [`CHAIN_SPUR_EVERY`] nodes.
///
/// The spurs exist so the shape is not a straight line: a straight line makes
/// the recursive CTE's frontier exactly one row wide, which is a degenerate
/// case rather than a deep one, and the branching is what keeps the fold length
/// long *and* the frontier non-trivial. They are short so depth stays the
/// dominant property.
pub const CHAIN_SPUR_EVERY: usize = 10;
/// Near-complete: every ordered pair `i -> j`, `i != j`, up to
/// [`DENSE_SMALL_CAP`] nodes.
///
/// Capped rather than scaled, deliberately. Edge count is `n(n-1)`, so an
/// uncapped `dense_small` at a bench's default node count would be tens of
/// millions of edges and minutes of fixture construction — and the shape's
/// point is density at a *small* node count, which is where the `DISTINCT`
/// sort and the byte budget bind. A caller asking for more nodes than the cap
/// gets the cap, and `fixtures_tests` pins that so it is a stated property
/// rather than a surprise in a results table.
pub const DENSE_SMALL_CAP: usize = 300;
// ---------------------------------------------------------------------------
// Structural facts
// ---------------------------------------------------------------------------
/// What a shape *is*, computed from its edge set rather than asserted about it.
///
/// `simple_paths` is the discriminating number, and it needs a caveat stated
/// here rather than discovered later. It is the count of distinct **simple
/// paths** of length `<= depth` from the start node. That is *not* the row
/// count of the shipped `walk` CTE: T0.1 replaced `UNION ALL` + a `path` column
/// with a plain `UNION`, which dedupes on `(node_id, depth)` and bounds the
/// walk at `reached × (depth+1)`. Calling this "the CTE's row count" would be
/// D-070's error committed inside the file written to prevent it.
///
/// It is kept, and it is the right structural metric, for two reasons:
///
/// * It is what separates a tree from a graph. On a tree it equals `reached`;
/// on [`Shape::DenseSmall`] it is four orders larger over the same node
/// count. Any cost that is multiplicative in branching per hop — path
/// enumeration, and anything downstream that materialises per-path state —
/// tracks it, and is identically flat on the fixture everything used to be
/// measured on.
/// * It is the cost that **returns** the moment path semantics are
/// reintroduced, which T0.1's own rustdoc argues is a live risk (the two
/// forms have equal reachability, so a reviewer can swap them and every test
/// still passes).
/// Compute [`Facts`] for a shape from its edge set.
///
/// A deliberate second implementation of the recursion, in Rust, over the same
/// edges the database is given — a shape whose properties are only knowable by
/// running the query they are meant to characterise cannot pin anything.
///
/// The cycle rule is the **pre-T0.1** one: a path may not revisit a node it
/// already contains. That is what makes `simple_paths` the number it is; see
/// [`Facts`]. Without a cycle rule of some kind this does not terminate on
/// [`Shape::Clustered`] or [`Shape::DenseSmall`], both of which have cycles.
/// The smallest depth from which the shape's start node reaches `fraction` of
/// its nodes, or `cap` if it never does.
///
/// **A fixed depth is not a comparable unit of work across shapes**, and this
/// is the correction that makes cross-shape tables mean anything. Measured at
/// depth 3 over 600 requested nodes, the four shapes reach 600, 24, 5 and 300
/// nodes respectively. A table indexed by depth is therefore comparing a
/// 600-node problem against a 5-node one and reporting the difference as a
/// property of the shape — which is D-070's error in a new place: a frame
/// nobody checked.
///
/// So a shape-crossing measurement states which it holds fixed. Depth is the
/// right control for "what does one more hop cost"; this is the right control
/// for "what does this shape cost at comparable size".
/// Nodes reachable from the shape's start within `depth` hops.
/// Cumulative reach after `0, 1, … max_depth` hops, from one BFS.
///
/// Deliberately **not** derived from [`facts`], even though the two agree.
/// `facts` enumerates simple paths, which is `O(simple_paths)` — 26.5 million
/// at depth 3 on [`Shape::DenseSmall`] and unbounded above that. And it is one
/// sweep rather than a probe per depth, because [`Shape::Chain`] needs `nodes`
/// hops to cover itself by construction, so a probe loop would be
/// `O(edges × depth²)` on exactly the shape whose point is that depth is large.
///
/// The frontier stops early when it empties; the returned vector is padded to
/// `max_depth + 1` so a caller can index it by depth without a bounds check.
// ---------------------------------------------------------------------------
// Seeding
// ---------------------------------------------------------------------------
/// Write a shape's concepts and edges into `db`.
///
/// Chunked at 2,000, which is what the bench seeders already used — the bulk
/// paths chunk internally anyway ([`Database::bulk_import`]), so this only
/// bounds the size of the `Vec` handed across the channel.
pub async