car-topology 0.55.0

Amortized coordination-topology selection core for Common Agent Runtime
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
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
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
//! The three measurements that decide whether this design is right for *your*
//! records.
//!
//! The paper's argument is empirical, not architectural: it does not claim a
//! codebook is inherently better than a decoder, it measures three properties
//! of the data and shows the incumbent design is misaligned with them. Those
//! measurements are workload properties, so they can be false for a workload
//! that is not the paper's — CAR's coordination runs are not GSM8K.
//!
//! This module is therefore the honest half of the crate. Run it on your own
//! execution records before trusting any of the machinery around it:
//!
//! 1. [`design_space_collapse`] — does the reward-surviving set stay small as
//!    codebook capacity grows? If it does not, an index is the wrong model and
//!    a generative decoder might genuinely earn its cost.
//! 2. [`edge_count_token_correlation`] — is `|E|` positively or negatively
//!    correlated with measured tokens? A negative `r` means every published
//!    designer's structural cost head is pointed the wrong way on your data.
//! 3. [`team_homogeneity`] — do your agents share a profile? If so, a
//!    message-passing scorer over profile nodes cannot rank candidates at all,
//!    and any system built on one is selecting by its cost head alone.

use serde::{Deserialize, Serialize};

use crate::codebook::{Codebook, CodebookConfig};
use crate::error::TopologyError;
use crate::record::RecordSet;

/// One point of the capacity sweep.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct CapacityPoint {
    /// The `K` the codebook was fitted at.
    pub capacity: usize,
    /// Codes that ended up with at least one survivor assigned.
    pub used_codes: usize,
    /// Distinct reward-surviving topologies in the records — the ceiling
    /// `used_codes` can reach, and constant across the sweep.
    pub distinct_survivors: usize,
}

/// Result of the capacity sweep.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CollapseReport {
    pub points: Vec<CapacityPoint>,
    /// Largest `used_codes` seen across the sweep.
    pub peak_used_codes: usize,
    /// True when growing capacity stopped buying used codes — the paper's
    /// finding, restated as a property of these records.
    pub collapsed: bool,
}

/// Fit a codebook at each capacity and report how many codes each fit actually
/// used.
///
/// The paper sweeps `K ∈ {4, …, 64}` and finds at most six codes in use for
/// every `K ≥ 8`. `collapsed` is true here when the largest capacity in the
/// sweep uses no more codes than half its capacity — i.e. extra capacity went
/// idle rather than finding more structure.
pub fn design_space_collapse(
    records: &RecordSet,
    capacities: &[usize],
    base: &CodebookConfig,
) -> Result<CollapseReport, TopologyError> {
    if capacities.is_empty() {
        return Err(TopologyError::BadConfig {
            field: "capacities",
            expected: "at least one capacity to sweep",
            found: "empty".into(),
        });
    }

    let mut points = Vec::with_capacity(capacities.len());
    for &capacity in capacities {
        let book = Codebook::fit(
            records,
            &CodebookConfig {
                capacity,
                ..base.clone()
            },
        )?;
        points.push(CapacityPoint {
            capacity,
            used_codes: book.used_codes(),
            distinct_survivors: book.distinct_survivors(),
        });
    }

    let peak_used_codes = points.iter().map(|p| p.used_codes).max().unwrap_or(0);
    let largest = points
        .iter()
        .max_by_key(|p| p.capacity)
        .expect("non-empty capacities");
    let collapsed = largest.used_codes * 2 <= largest.capacity;

    Ok(CollapseReport {
        points,
        peak_used_codes,
        collapsed,
    })
}

/// What the edge-count surrogate is doing on a set of records.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(tag = "verdict", rename_all = "snake_case")]
pub enum CostSurrogateVerdict {
    /// `r < 0` — more edges, fewer measured tokens. Minimizing `|E|` increases
    /// spend. This is the paper's finding (`r ≈ -0.4`).
    Inverted { r: f32 },
    /// `r > 0` — `|E|` tracks tokens, so the incumbent surrogate points the
    /// right way on this workload even though it is still a proxy for the
    /// thing you can measure directly.
    Aligned { r: f32 },
    /// Not enough variation in `|E|` or in tokens to compute a correlation:
    /// every record shares an edge count, or every record shares a token count.
    Uninformative,
}

/// Pearson correlation between `|E|` and per-task normalized token cost.
///
/// Normalized rather than raw tokens on purpose: raw tokens are dominated by
/// task difficulty, so a set whose hard tasks happened to run under dense
/// graphs would show a spurious positive `r`. Normalizing within task is what
/// makes the correlation a statement about topology.
pub fn edge_count_token_correlation(records: &RecordSet) -> CostSurrogateVerdict {
    let xs: Vec<f64> = records
        .records()
        .iter()
        .map(|r| r.topology.edge_count() as f64)
        .collect();
    let ys: Vec<f64> = records
        .normalized_costs()
        .iter()
        .map(|&c| c as f64)
        .collect();

    match pearson(&xs, &ys) {
        None => CostSurrogateVerdict::Uninformative,
        Some(r) if r < 0.0 => CostSurrogateVerdict::Inverted { r: r as f32 },
        Some(r) => CostSurrogateVerdict::Aligned { r: r as f32 },
    }
}

/// Pearson `r`, or `None` when either series has no variance.
fn pearson(xs: &[f64], ys: &[f64]) -> Option<f64> {
    if xs.len() != ys.len() || xs.len() < 2 {
        return None;
    }
    let n = xs.len() as f64;
    let mx = xs.iter().sum::<f64>() / n;
    let my = ys.iter().sum::<f64>() / n;
    let mut cov = 0.0;
    let mut vx = 0.0;
    let mut vy = 0.0;
    for (x, y) in xs.iter().zip(ys.iter()) {
        let dx = x - mx;
        let dy = y - my;
        cov += dx * dy;
        vx += dx * dx;
        vy += dy * dy;
    }
    if vx <= f64::EPSILON || vy <= f64::EPSILON {
        return None;
    }
    let r = cov / (vx.sqrt() * vy.sqrt());
    r.is_finite().then_some(r)
}

/// Whether a team's agents are distinguishable by their profile embeddings.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Homogeneity {
    /// Every profile embedding is identical within tolerance. A message-passing
    /// scorer over these nodes is constant in `A`.
    Homogeneous,
    /// Profiles differ. A message-passing scorer *can* distinguish adjacencies,
    /// though nothing here says it does so well.
    Heterogeneous,
}

/// What a profile-node message-passing scorer can and cannot do on this team.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ScorerAdvice {
    pub homogeneity: Homogeneity,
    /// True when a message-passing scorer over these profile nodes assigns
    /// every candidate topology the same score.
    pub message_passing_is_adjacency_blind: bool,
    /// A one-line explanation suitable for logging next to a selection.
    pub reason: String,
}

/// Classify a team by whether its agents carry distinguishable profiles.
///
/// `tolerance` is the per-component absolute difference below which two
/// profiles count as the same. `1e-6` is a reasonable default for embeddings
/// that came from the same encoder on the same text.
pub fn team_homogeneity(profiles: &[Vec<f32>], tolerance: f32) -> Homogeneity {
    let Some(first) = profiles.first() else {
        // A team with no profiles is trivially indistinguishable.
        return Homogeneity::Homogeneous;
    };
    for profile in profiles.iter().skip(1) {
        if profile.len() != first.len() {
            return Homogeneity::Heterogeneous;
        }
        if profile
            .iter()
            .zip(first.iter())
            .any(|(a, b)| (a - b).abs() > tolerance)
        {
            return Homogeneity::Heterogeneous;
        }
    }
    Homogeneity::Homogeneous
}

/// The guard to run before adopting — or trusting — a profile-node
/// message-passing scorer.
///
/// On a homogeneous team the answer is unconditional and does not depend on the
/// architecture: mean, sum, and attention aggregators all fold identical node
/// features, so the pooled score cannot vary with `A`. Any ranking such a
/// system produces comes from its structural cost head alone — which, if that
/// head is the edge count, is the head this crate measures to be inverted.
pub fn scorer_advice(profiles: &[Vec<f32>], tolerance: f32) -> ScorerAdvice {
    let homogeneity = team_homogeneity(profiles, tolerance);
    match homogeneity {
        Homogeneity::Homogeneous => ScorerAdvice {
            homogeneity,
            message_passing_is_adjacency_blind: true,
            reason: format!(
                "all {} agent profiles are identical, so message passing over profile nodes \
                 pools the same features for every candidate and cannot rank adjacencies; \
                 score on the flattened adjacency instead",
                profiles.len()
            ),
        },
        Homogeneity::Heterogeneous => ScorerAdvice {
            homogeneity,
            message_passing_is_adjacency_blind: false,
            reason: "agent profiles differ, so message passing over profile nodes can in \
                     principle distinguish adjacencies"
                .into(),
        },
    }
}

/// Capacities swept by [`diagnose`] when the caller does not name their own —
/// the paper's own sweep.
pub const DEFAULT_CAPACITY_SWEEP: [usize; 5] = [4, 8, 16, 32, 64];

/// The paper's collection protocol: 50 training tasks under 6 fixed topologies.
pub const PROTOCOL_TASKS: usize = 50;
/// Records the protocol yields — `PROTOCOL_TASKS` × 6 topologies.
pub const PROTOCOL_RECORDS: usize = PROTOCOL_TASKS * 6;

/// Whether there is enough data behind a report to read it as anything.
///
/// Reported rather than enforced. This repo has been bitten before by acting on
/// a measurement whose sample could not support it (see the ±15pp per-track
/// noise floor in `CLAUDE.md`), so the honest move is to put the sample size
/// next to the verdict instead of letting a six-record journal read like a
/// finding.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "sufficiency", rename_all = "snake_case")]
pub enum Sufficiency {
    /// At or above the paper's collection protocol.
    AtProtocol,
    /// Below the protocol but enough to compute every measurement.
    Thin { records: usize, tasks: usize },
    /// Too few to compute a measurement at all — a correlation needs varying
    /// edge counts and varying costs, and a collapse sweep needs survivors.
    Insufficient { records: usize, tasks: usize },
}

impl Sufficiency {
    /// Whether the measurements below rest on at least the paper's own sample.
    pub fn at_protocol(self) -> bool {
        matches!(self, Sufficiency::AtProtocol)
    }
}

/// All three of the paper's claims, measured on one record set.
///
/// [`diagnose`] fills in the two that records alone can answer.
/// `scorer` — the homogeneity claim — needs the team's agent profiles, which an
/// [`ExecutionRecord`](crate::ExecutionRecord) does not carry, so it stays
/// `None` unless you call [`diagnose_with_profiles`]. That gap is stated rather
/// than papered over: a report that silently omitted the claim most likely to
/// hold for CAR would be worse than one that names what it could not measure.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DiagnosticsReport {
    /// Records the report was computed over.
    pub records: usize,
    /// Distinct tasks those records span.
    pub tasks: usize,
    /// Team size every record shares.
    pub team_size: usize,
    /// The encoder the record set is labeled with, when it is labeled.
    pub embedder: Option<String>,
    /// Whether the sample can support the verdicts below.
    pub sufficiency: Sufficiency,
    /// Claim 1 — does the reward-surviving space stay short as capacity grows?
    pub collapse: CollapseReport,
    /// Claim 2 — is edge count an inverted cost surrogate here?
    pub cost_surrogate: CostSurrogateVerdict,
    /// Claim 3 — would a profile-node message-passing scorer be able to rank
    /// anything? `None` when no profiles were supplied.
    pub scorer: Option<ScorerAdvice>,
}

impl DiagnosticsReport {
    /// One factual sentence naming what the two record-derived claims found.
    ///
    /// Deliberately descriptive, not a recommendation: whether to adopt
    /// amortized selection is a judgment about a workload, and two verdicts
    /// over one journal are an input to it, not the answer.
    pub fn headline(&self) -> String {
        let collapse = if self.collapse.collapsed {
            format!(
                "the reward-surviving space stayed at {} codes as capacity grew to {}",
                self.collapse.peak_used_codes,
                self.collapse
                    .points
                    .iter()
                    .map(|p| p.capacity)
                    .max()
                    .unwrap_or(0)
            )
        } else {
            format!(
                "the reward-surviving space kept using capacity (peak {} codes)",
                self.collapse.peak_used_codes
            )
        };
        let surrogate = match self.cost_surrogate {
            CostSurrogateVerdict::Inverted { r } => {
                format!("edge count is an INVERTED cost surrogate (r = {r:.2})")
            }
            CostSurrogateVerdict::Aligned { r } => {
                format!("edge count tracks measured tokens (r = {r:.2})")
            }
            CostSurrogateVerdict::Uninformative => {
                "edge count vs tokens is uninformative here".to_string()
            }
        };
        format!("{collapse}; {surrogate}")
    }

    /// A plain-text rendering for a log line or an operator's terminal.
    pub fn render(&self) -> String {
        let mut out = String::new();
        out.push_str("topology diagnostics\n");
        out.push_str(&format!(
            "  records: {} over {} tasks, team size {}{}\n",
            self.records,
            self.tasks,
            self.team_size,
            match &self.embedder {
                Some(e) => format!(", embedder {e}"),
                None => ", embedder unrecorded".to_string(),
            }
        ));
        out.push_str(&match self.sufficiency {
            Sufficiency::AtProtocol => format!(
                "  sample: at or above the paper's protocol ({PROTOCOL_RECORDS} records over \
                 {PROTOCOL_TASKS} tasks)\n"
            ),
            Sufficiency::Thin { records, tasks } => format!(
                "  sample: THIN — {records} records over {tasks} tasks, below the paper's \
                 protocol ({PROTOCOL_RECORDS} over {PROTOCOL_TASKS}); read the verdicts below \
                 as provisional\n"
            ),
            Sufficiency::Insufficient { records, tasks } => format!(
                "  sample: INSUFFICIENT — {records} records over {tasks} tasks; a verdict below \
                 may rest on no variation at all\n"
            ),
        });

        out.push_str("\n  [1] does the useful design space stay a short list?\n");
        for point in &self.collapse.points {
            out.push_str(&format!(
                "      capacity {:>3} -> {:>3} codes used\n",
                point.capacity, point.used_codes
            ));
        }
        out.push_str(&format!(
            "      {} distinct reward-surviving topologies; {}\n",
            self.collapse
                .points
                .first()
                .map(|p| p.distinct_survivors)
                .unwrap_or(0),
            if self.collapse.collapsed {
                "COLLAPSED — extra capacity went idle, so an index fits"
            } else {
                "NOT collapsed — capacity kept buying codes, so a short list may be the wrong \
                 model for this workload"
            }
        ));

        out.push_str("\n  [2] is edge count an inverted cost surrogate?\n");
        out.push_str(&match self.cost_surrogate {
            CostSurrogateVerdict::Inverted { r } => format!(
                "      INVERTED (Pearson r = {r:.3}) — minimizing |E| raises measured tokens; \
                 rank on measured cost, never on structure\n"
            ),
            CostSurrogateVerdict::Aligned { r } => format!(
                "      aligned (Pearson r = {r:.3}) — |E| tracks tokens here, though it is \
                 still a proxy for something you can measure directly\n"
            ),
            CostSurrogateVerdict::Uninformative => "      uninformative — edge count or token \
                 cost does not vary across these records\n"
                .to_string(),
        });

        out.push_str("\n  [3] can a profile-node message-passing scorer rank anything?\n");
        out.push_str(&match &self.scorer {
            Some(advice) => format!("      {}\n", advice.reason),
            None => "      NOT MEASURED — needs the team's agent profiles, which execution \
                 records do not carry. Pass them to diagnose_with_profiles, or use \
                 car_multi::topology::scorer_advice over the AgentSpecs.\n"
                .to_string(),
        });
        out
    }
}

/// Measure the two claims a record set can answer on its own.
///
/// `capacities` defaults to [`DEFAULT_CAPACITY_SWEEP`] when empty.
pub fn diagnose(
    records: &RecordSet,
    capacities: &[usize],
    base: &CodebookConfig,
) -> Result<DiagnosticsReport, TopologyError> {
    diagnose_inner(records, capacities, base, None)
}

/// The same report, plus the homogeneity claim, for a caller that has the
/// team's agent-profile embeddings.
///
/// `tolerance` is the per-component difference below which two profiles count
/// as the same; `1e-6` suits embeddings from one encoder over the same text.
pub fn diagnose_with_profiles(
    records: &RecordSet,
    capacities: &[usize],
    base: &CodebookConfig,
    profiles: &[Vec<f32>],
    tolerance: f32,
) -> Result<DiagnosticsReport, TopologyError> {
    diagnose_inner(
        records,
        capacities,
        base,
        Some(scorer_advice(profiles, tolerance)),
    )
}

fn diagnose_inner(
    records: &RecordSet,
    capacities: &[usize],
    base: &CodebookConfig,
    scorer: Option<ScorerAdvice>,
) -> Result<DiagnosticsReport, TopologyError> {
    let sweep: Vec<usize> = if capacities.is_empty() {
        DEFAULT_CAPACITY_SWEEP.to_vec()
    } else {
        capacities.to_vec()
    };
    let collapse = design_space_collapse(records, &sweep, base)?;
    let cost_surrogate = edge_count_token_correlation(records);

    let tasks = records.task_ids().len();
    let count = records.len();
    // "Nothing varied" is tested FIRST, and deliberately. A set can clear the
    // paper's record and task counts and still support no verdict at all —
    // 360 records over 60 tasks under a single topology has nothing for the
    // correlation to read and leaves the collapse sweep resting on one code.
    // Ordering the count rule first reported that as `AtProtocol` while the
    // report itself rendered "uninformative", which is exactly the
    // large-but-degenerate sample this field exists to flag.
    let sufficiency = if matches!(cost_surrogate, CostSurrogateVerdict::Uninformative)
        || collapse.peak_used_codes < 2
    {
        Sufficiency::Insufficient {
            records: count,
            tasks,
        }
    } else if count >= PROTOCOL_RECORDS && tasks >= PROTOCOL_TASKS {
        Sufficiency::AtProtocol
    } else {
        Sufficiency::Thin {
            records: count,
            tasks,
        }
    };

    Ok(DiagnosticsReport {
        records: count,
        tasks,
        team_size: records.team_size(),
        embedder: records.embedder().map(str::to_owned),
        sufficiency,
        collapse,
        cost_surrogate,
        scorer,
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::record::ExecutionRecord;
    use crate::topology::Topology;

    fn records_with(topologies: Vec<(Topology, u64)>) -> RecordSet {
        let mut out = Vec::new();
        for i in 0..4 {
            for (t, tokens) in &topologies {
                out.push(ExecutionRecord::new(
                    format!("t{i}"),
                    vec![0.5, 0.5],
                    t.clone(),
                    1.0,
                    *tokens,
                ));
            }
        }
        RecordSet::new(out).unwrap()
    }

    #[test]
    fn collapse_report_shows_idle_capacity() {
        let n = 4;
        let records = records_with(
            Topology::collection_protocol(n)
                .unwrap()
                .into_iter()
                .map(|t| (t, 500))
                .collect(),
        );
        let report =
            design_space_collapse(&records, &[4, 8, 16, 32, 64], &CodebookConfig::default())
                .unwrap();
        assert_eq!(report.peak_used_codes, 6);
        assert!(report.collapsed);
        // Capacity 4 is the only point that binds.
        assert_eq!(report.points[0].used_codes, 4);
        for point in &report.points[1..] {
            assert_eq!(point.used_codes, 6, "capacity {} used more", point.capacity);
        }
    }

    #[test]
    fn a_wide_design_space_does_not_report_collapse() {
        // 40 distinct survivors: capacity keeps buying codes.
        let n = 6;
        let mut out = Vec::new();
        for seed in 0..40u64 {
            out.push(ExecutionRecord::new(
                format!("t{seed}"),
                vec![0.5, 0.5],
                Topology::erdos_renyi(n, 0.5, seed + 1).unwrap(),
                1.0,
                500,
            ));
        }
        let records = RecordSet::new(out).unwrap();
        let report =
            design_space_collapse(&records, &[4, 8, 16, 32], &CodebookConfig::default()).unwrap();
        assert!(!report.collapsed);
        assert_eq!(report.peak_used_codes, 32);
    }

    #[test]
    fn an_empty_capacity_sweep_is_rejected() {
        let records = records_with(vec![(Topology::chain(4).unwrap(), 100)]);
        assert!(matches!(
            design_space_collapse(&records, &[], &CodebookConfig::default()),
            Err(TopologyError::BadConfig { .. })
        ));
    }

    #[test]
    fn the_inverted_surrogate_is_detected() {
        // Sparse chain is dear, dense complete is cheap — the paper's regime.
        let n = 4;
        let records = records_with(vec![
            (Topology::chain(n).unwrap(), 3000),
            (Topology::star(n, 0).unwrap(), 1500),
            (Topology::complete(n).unwrap(), 900),
        ]);
        match edge_count_token_correlation(&records) {
            CostSurrogateVerdict::Inverted { r } => assert!(r < -0.5, "r = {r}"),
            other => panic!("expected Inverted, got {other:?}"),
        }
    }

    #[test]
    fn an_aligned_surrogate_is_reported_as_such() {
        let n = 4;
        let records = records_with(vec![
            (Topology::chain(n).unwrap(), 500),
            (Topology::star(n, 0).unwrap(), 1500),
            (Topology::complete(n).unwrap(), 3000),
        ]);
        assert!(matches!(
            edge_count_token_correlation(&records),
            CostSurrogateVerdict::Aligned { .. }
        ));
    }

    #[test]
    fn a_constant_series_is_uninformative_not_a_nan() {
        let n = 4;
        let records = records_with(vec![(Topology::chain(n).unwrap(), 500)]);
        assert_eq!(
            edge_count_token_correlation(&records),
            CostSurrogateVerdict::Uninformative
        );
    }

    #[test]
    fn identical_profiles_make_message_passing_adjacency_blind() {
        let profile = vec![0.1, 0.2, 0.3];
        let team = vec![profile.clone(), profile.clone(), profile.clone(), profile];
        let advice = scorer_advice(&team, 1e-6);
        assert_eq!(advice.homogeneity, Homogeneity::Homogeneous);
        assert!(advice.message_passing_is_adjacency_blind);
        assert!(advice.reason.contains("cannot rank"));
    }

    #[test]
    fn differing_profiles_are_heterogeneous() {
        let team = vec![vec![0.1, 0.2], vec![0.9, 0.8]];
        let advice = scorer_advice(&team, 1e-6);
        assert_eq!(advice.homogeneity, Homogeneity::Heterogeneous);
        assert!(!advice.message_passing_is_adjacency_blind);
    }

    #[test]
    fn tolerance_absorbs_encoder_noise_but_not_real_differences() {
        let team = vec![vec![0.1, 0.2], vec![0.1 + 1e-8, 0.2 - 1e-8]];
        assert_eq!(team_homogeneity(&team, 1e-6), Homogeneity::Homogeneous);
        assert_eq!(team_homogeneity(&team, 1e-12), Homogeneity::Heterogeneous);
    }

    #[test]
    fn mismatched_profile_lengths_are_heterogeneous() {
        let team = vec![vec![0.1, 0.2], vec![0.1]];
        assert_eq!(team_homogeneity(&team, 1e-6), Homogeneity::Heterogeneous);
    }

    #[test]
    fn an_empty_team_is_homogeneous() {
        assert_eq!(team_homogeneity(&[], 1e-6), Homogeneity::Homogeneous);
    }

    /// The paper's regime, as one report: collapse holds and the surrogate is
    /// inverted.
    fn papers_regime() -> RecordSet {
        let mut out = Vec::new();
        for task in 0..10 {
            for topology in Topology::collection_protocol(4).unwrap() {
                let edges = topology.edge_count() as u64;
                out.push(ExecutionRecord::new(
                    format!("t{task}"),
                    vec![task as f32 / 10.0, 1.0 - task as f32 / 10.0],
                    topology,
                    1.0,
                    3200u64.saturating_sub(180 * edges),
                ));
            }
        }
        RecordSet::new(out).unwrap()
    }

    #[test]
    fn the_report_folds_both_record_derived_claims() {
        let report = diagnose(&papers_regime(), &[], &CodebookConfig::default()).unwrap();
        assert!(report.collapse.collapsed);
        assert!(matches!(
            report.cost_surrogate,
            CostSurrogateVerdict::Inverted { .. }
        ));
        assert_eq!(report.records, 60);
        assert_eq!(report.tasks, 10);
        assert_eq!(report.team_size, 4);
    }

    #[test]
    fn the_homogeneity_claim_is_absent_until_profiles_are_supplied() {
        let records = papers_regime();
        let bare = diagnose(&records, &[], &CodebookConfig::default()).unwrap();
        assert!(
            bare.scorer.is_none(),
            "records do not carry profiles, so the claim must read as unmeasured"
        );
        assert!(bare.render().contains("NOT MEASURED"));

        let profile = vec![0.2, 0.4];
        let team = vec![profile.clone(); 4];
        let full =
            diagnose_with_profiles(&records, &[], &CodebookConfig::default(), &team, 1e-6).unwrap();
        assert!(!full.render().contains("NOT MEASURED"));
        let advice = full.scorer.expect("profiles were supplied");
        assert!(advice.message_passing_is_adjacency_blind);
    }

    #[test]
    fn the_report_carries_the_embedder_label_when_the_set_has_one() {
        let records =
            RecordSet::with_embedder(papers_regime().records().to_vec(), "mini-lm").unwrap();
        let report = diagnose(&records, &[], &CodebookConfig::default()).unwrap();
        assert_eq!(report.embedder.as_deref(), Some("mini-lm"));
        assert!(report.render().contains("embedder mini-lm"));

        let unlabeled = diagnose(&papers_regime(), &[], &CodebookConfig::default()).unwrap();
        assert!(unlabeled.render().contains("embedder unrecorded"));
    }

    #[test]
    fn a_thin_sample_is_flagged_rather_than_read_as_a_finding() {
        let report = diagnose(&papers_regime(), &[], &CodebookConfig::default()).unwrap();
        // 60 records over 10 tasks — well under the paper's 300 over 50.
        assert!(matches!(
            report.sufficiency,
            Sufficiency::Thin {
                records: 60,
                tasks: 10
            }
        ));
        assert!(!report.sufficiency.at_protocol());
        assert!(report.render().contains("THIN"));
    }

    #[test]
    fn a_protocol_sized_sample_is_not_flagged() {
        let mut out = Vec::new();
        for task in 0..PROTOCOL_TASKS {
            for topology in Topology::collection_protocol(4).unwrap() {
                let edges = topology.edge_count() as u64;
                out.push(ExecutionRecord::new(
                    format!("t{task}"),
                    vec![task as f32 / PROTOCOL_TASKS as f32, 0.5],
                    topology,
                    1.0,
                    3200u64.saturating_sub(180 * edges),
                ));
            }
        }
        let records = RecordSet::new(out).unwrap();
        assert_eq!(records.len(), PROTOCOL_RECORDS);
        let report = diagnose(&records, &[], &CodebookConfig::default()).unwrap();
        assert!(report.sufficiency.at_protocol());
        assert!(report.render().contains("at or above the paper's protocol"));
    }

    #[test]
    fn a_sample_with_nothing_to_vary_over_is_insufficient_not_a_verdict() {
        // One topology, one cost: the correlation has no variance to read.
        let records = records_with(vec![(Topology::chain(4).unwrap(), 500)]);
        let report = diagnose(&records, &[], &CodebookConfig::default()).unwrap();
        assert!(matches!(
            report.sufficiency,
            Sufficiency::Insufficient { .. }
        ));
        assert_eq!(report.cost_surrogate, CostSurrogateVerdict::Uninformative);
        assert!(report.render().contains("INSUFFICIENT"));
    }

    #[test]
    fn the_headline_names_what_each_claim_found() {
        let inverted = diagnose(&papers_regime(), &[], &CodebookConfig::default()).unwrap();
        let headline = inverted.headline();
        assert!(headline.contains("INVERTED"), "{headline}");
        assert!(headline.contains("stayed at"), "{headline}");

        let aligned = records_with(vec![
            (Topology::chain(4).unwrap(), 500),
            (Topology::star(4, 0).unwrap(), 1500),
            (Topology::complete(4).unwrap(), 3000),
        ]);
        let headline = diagnose(&aligned, &[], &CodebookConfig::default())
            .unwrap()
            .headline();
        assert!(headline.contains("tracks measured tokens"), "{headline}");
    }

    #[test]
    fn an_empty_capacity_sweep_falls_back_to_the_papers_own() {
        let report = diagnose(&papers_regime(), &[], &CodebookConfig::default()).unwrap();
        let swept: Vec<usize> = report.collapse.points.iter().map(|p| p.capacity).collect();
        assert_eq!(swept, DEFAULT_CAPACITY_SWEEP.to_vec());
    }

    /// The render's prose is assembled from `\`-continued string literals, and a
    /// mangled continuation leaves a run of spaces mid-sentence that is
    /// invisible in the source and obvious in an operator's terminal. (That is
    /// not hypothetical — it shipped into this function once.) Pin the joins.
    #[test]
    fn the_rendered_report_has_no_mangled_line_continuations() {
        let profile = vec![0.2, 0.4];
        let team = vec![profile; 4];
        let report = diagnose_with_profiles(
            &papers_regime(),
            &[],
            &CodebookConfig::default(),
            &team,
            1e-6,
        )
        .unwrap();
        let rendered = report.render();

        // Each phrase spans a continuation; a swallowed `\` splits it.
        for phrase in [
            "below the paper's protocol (300 over 50); read the verdicts below as provisional",
            "raises measured tokens; rank on measured cost, never on structure",
            "pools the same features for every candidate and cannot rank adjacencies",
        ] {
            assert!(rendered.contains(phrase), "missing or split: {phrase:?}");
        }

        // No prose line carries a stray gap. The capacity rows are exempt:
        // their runs of spaces are deliberate `{:>3}` column padding.
        for line in rendered.lines() {
            if line.trim_start().starts_with("capacity ") {
                continue;
            }
            assert!(
                !line.trim_start().contains("  "),
                "stray whitespace run in: {line:?}"
            );
        }
    }

    #[test]
    fn every_verdict_variant_renders_without_a_stray_gap() {
        // Aligned and Uninformative sit on branches the fixture above never
        // takes, and each is its own set of continuations.
        let aligned = records_with(vec![
            (Topology::chain(4).unwrap(), 500),
            (Topology::star(4, 0).unwrap(), 1500),
            (Topology::complete(4).unwrap(), 3000),
        ]);
        let flat = records_with(vec![(Topology::chain(4).unwrap(), 500)]);
        for records in [aligned, flat] {
            let rendered = diagnose(&records, &[], &CodebookConfig::default())
                .unwrap()
                .render();
            for line in rendered.lines() {
                if line.trim_start().starts_with("capacity ") {
                    continue;
                }
                assert!(
                    !line.trim_start().contains("  "),
                    "stray whitespace run in: {line:?}"
                );
            }
        }
    }

    #[test]
    fn the_report_round_trips_through_json() {
        let report = diagnose(&papers_regime(), &[], &CodebookConfig::default()).unwrap();
        let json = serde_json::to_string(&report).unwrap();
        let back: DiagnosticsReport = serde_json::from_str(&json).unwrap();
        assert_eq!(report, back);
    }
}