gunnar-server 3.0.0

The gunnar git server: SSH and smart-HTTP git transports and the gRPC control plane. The appliance half, extracted from gunnar-cli so the server code lives in its own crate.
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
//! **`Admin.Capacity` on a REAL filesystem: the number is measured, the "no"
//! arm is reachable, and an unmeasured box does not say yes (measured, not asserted).**
//!
//! `capacity.rs`'s own unit tests drive the decision over recorded appliance
//! facts — that is the only way to reach `GROWTH_SEALED` on a developer box,
//! where gunnar is never PID 1. This file proves the half a fixture cannot:
//! that the bytes on the wire come from a live `statvfs` and not from a
//! constant.
//!
//! **How it tells a working `statvfs` from a hardcoded number.** Not by
//! comparing against an expected value — any constant satisfies a comparison
//! with itself, and an expected value of 0 satisfies almost anything. It
//! writes 64 MiB into the store root and asks again: a live measurement moves
//! by what was written, and no constant can follow it. That is applied output
//! off a real filesystem, measured before and after a real write.

use std::sync::Arc;

use gunnar_core::{NewRepo, RepoRef};
use gunnar_grpc::{ControlError, ControlPlane as _};
use gunnar_proto::{CapacityRequest, CapacityVerdict, SetGrowth};
use gunnar_server::control_plane::CoreControlPlane;
use gunnar_server::stores::ServerStores;
use gunnar_ssh::{DiskFloor, DiskHeadroomGate};
use prost::Message as _;

const MIB: u64 = 1024 * 1024;
const WRITTEN: u64 = 64 * MIB;

struct Rig {
    _dir: tempfile::TempDir,
    stores: Arc<ServerStores>,
}

impl Rig {
    fn new() -> Rig {
        let dir = tempfile::tempdir().expect("tempdir");
        let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
        stores
            .catalog()
            .create_namespace("team")
            .expect("namespace");
        Rig { _dir: dir, stores }
    }

    fn gate(&self, floor: DiskFloor) -> Arc<DiskHeadroomGate> {
        Arc::new(DiskHeadroomGate::new(self.stores.root(), floor))
    }

    fn plane(&self, floor: DiskFloor) -> CoreControlPlane {
        CoreControlPlane::new(Arc::clone(&self.stores)).with_headroom(self.gate(floor))
    }

    /// Actually consume `bytes` of the store root's filesystem, flushed.
    fn burn(&self, name: &str, bytes: u64) {
        use std::io::Write as _;
        let path = self.stores.root().join(name);
        let mut f = std::fs::File::create(&path).expect("create");
        let chunk = vec![0x5au8; MIB as usize];
        let mut left = bytes;
        while left > 0 {
            let n = left.min(MIB) as usize;
            f.write_all(&chunk[..n]).expect("write");
            left -= n as u64;
        }
        f.flush().expect("flush");
        f.sync_all()
            .expect("fsync: the bytes must be on the filesystem, not in a page cache");
    }
}

fn ask(
    plane: &CoreControlPlane,
    requested: u64,
    namespace: &str,
) -> gunnar_proto::CapacityResponse {
    plane
        .capacity(&CapacityRequest {
            requested_bytes: requested,
            namespace: namespace.to_string(),
            disks: false,
        })
        .expect("a plane with a disk gate answers")
}

/// **The headroom number is the REAL one.** Proven by moving it: 64 MiB
/// written into the store root must show up as 64 MiB less free and 64 MiB
/// less servable. A hardcoded constant — or a `statvfs` of the wrong path —
/// cannot follow a write it did not see.
#[test]
fn the_free_bytes_are_a_live_statvfs_and_move_when_the_filesystem_moves() {
    let rig = Rig::new();
    let plane = rig.plane(DiskFloor {
        percent: 0,
        bytes: 1,
    });

    let before = ask(&plane, 1, "");
    // A real filesystem has a size, and it is not all free — if it were, the
    // "identity value" trap would be wide open here.
    assert!(before.total_bytes > 0, "{before:?}");
    assert!(
        before.free_bytes > 0 && before.free_bytes < before.total_bytes,
        "{before:?}"
    );
    assert_eq!(
        before.servable_bytes,
        before.free_bytes - before.floor_bytes,
        "servable is free minus the floor; quoting `free` would sell the reserve"
    );
    // The same measurement the push door refuses under, to the byte-ish.
    let gate = rig.gate(DiskFloor {
        percent: 0,
        bytes: 1,
    });
    let room = gate.headroom().expect("statvfs on the store root");
    assert_eq!(
        before.total_bytes, room.total,
        "the RPC and the disk gate must measure the SAME filesystem"
    );
    assert!(
        before.free_bytes.abs_diff(room.free) < 4 * MIB,
        "the RPC's free bytes and the gate's differ by more than a moment's noise: {} vs {}",
        before.free_bytes,
        room.free
    );

    rig.burn("ballast", WRITTEN);

    let after = ask(&plane, 1, "");
    let dropped = before.free_bytes.saturating_sub(after.free_bytes);
    assert!(
        dropped >= WRITTEN * 3 / 4,
        "writing {WRITTEN} bytes must be visible in the answer; free went {} -> {} (a drop of \
         {dropped}). A hardcoded constant cannot move, and neither can a statvfs of the wrong \
         path.",
        before.free_bytes,
        after.free_bytes
    );
    assert_eq!(
        after.total_bytes, before.total_bytes,
        "the filesystem did not change size, only its free space"
    );
    assert_eq!(
        before.servable_bytes.saturating_sub(after.servable_bytes),
        dropped,
        "servable must move with free, byte for byte"
    );
    assert_eq!(after.root, rig.stores.root().display().to_string());
}

/// **Both arms, on a real disk.** A raise that fits is YES; one that does not
/// is NO, with the shortfall named — and the shortfall is arithmetic over the
/// measured bytes, not a flag.
#[test]
fn a_raise_beyond_the_servable_bytes_is_answered_no_with_the_shortfall_named() {
    let rig = Rig::new();
    let plane = rig.plane(DiskFloor {
        percent: 0,
        bytes: 1,
    });

    // Green arm: one byte fits on any disk that has any room at all.
    let yes = ask(&plane, 1, "");
    assert_eq!(
        yes.verdict,
        CapacityVerdict::CapacityAvailable as i32,
        "{}",
        yes.reason
    );
    assert_eq!(yes.shortfall_bytes, 0);
    assert!(yes.reason.contains("YES"), "{}", yes.reason);

    // Red arm, reachable on a real filesystem: a petabyte is not there.
    let huge = 1024 * 1024 * 1024 * 1024 * 1024u64;
    let no = ask(&plane, huge, "");
    assert_eq!(
        no.verdict,
        CapacityVerdict::CapacityExhausted as i32,
        "{}",
        no.reason
    );
    assert_eq!(
        no.additional_bytes, huge,
        "no namespace was named, so the whole cap is new"
    );
    assert_eq!(
        no.shortfall_bytes,
        huge - no.servable_bytes,
        "the shortfall is what is MISSING, computed from the measured servable bytes"
    );
    assert!(no.shortfall_bytes > 0);
    assert!(
        no.reason.contains("NO."),
        "the sentence must say no: {}",
        no.reason
    );
    assert!(
        no.reason
            .contains(&gunnar_ssh::headroom::human(no.servable_bytes)),
        "and must name what CAN be served: {}",
        no.reason
    );

    // **The boundary is `servable`, not `free`: the floor is not for sale.**
    // A second plane whose floor is half the free space must refuse three
    // quarters of it — a request comfortably UNDER `free` and comfortably OVER
    // `free - floor`, so the margin swamps the drift a live filesystem has
    // between two calls. (The first draft of this test asked for exactly
    // `servable_bytes` and was red on the honest answer: 444 KiB had moved
    // under it between the two `statvfs` calls.)
    let free = no.free_bytes;
    let half = rig.plane(DiskFloor {
        percent: 0,
        bytes: free / 2,
    });
    let over = ask(&half, free / 4 * 3, "");
    assert_eq!(
        over.verdict,
        CapacityVerdict::CapacityExhausted as i32,
        "{} of {} free was accepted with a {} floor in force: the reserve was sold",
        gunnar_ssh::headroom::human(free / 4 * 3),
        gunnar_ssh::headroom::human(free),
        gunnar_ssh::headroom::human(free / 2),
    );
    let under = ask(&half, free / 4, "");
    assert_eq!(
        under.verdict,
        CapacityVerdict::CapacityAvailable as i32,
        "and the same disk, asked for a quarter, must still say yes — or this test proves          nothing but that the gate refuses everything: {}",
        under.reason
    );
    assert_eq!(
        under.floor_bytes,
        free / 2,
        "the floor reported is the one this plane enforces, not a default"
    );
}

/// **A raise needs the DELTA.** The bytes a tenant already holds are already
/// spent out of free space, so a tenant holding N asking for N + 1 needs one
/// byte — and `held_bytes` is the real meter, measured off the disk, not 0.
#[test]
fn a_named_namespace_is_only_charged_for_the_bytes_it_does_not_already_hold() {
    let rig = Rig::new();
    let path = RepoRef::new("team", "proj").expect("legal");
    rig.stores
        .create_repo(NewRepo::new(path.clone()), false)
        .expect("repo");
    // Real bytes in the repository's own directory, so `Usage.Read`'s walk
    // finds them. 8 MiB is far past any empty store's noise.
    let git_dir = rig.stores.repo_dir(&path).expect("a path");
    std::fs::write(git_dir.join("ballast.pack"), vec![0x5au8; 8 * MIB as usize]).expect("write");

    let plane = rig.plane(DiskFloor {
        percent: 0,
        bytes: 1,
    });
    let held = plane
        .usage(Some("team"))
        .expect("usage")
        .first()
        .expect("one row")
        .pack_bytes;
    assert!(
        held >= 8 * MIB,
        "this test needs the namespace to really hold bytes, not zero; it holds {held}"
    );

    let a = ask(&plane, held + 1, "team");
    assert_eq!(
        a.held_bytes, held,
        "the RPC must report the SAME meter Usage.Read does (one implementation)"
    );
    assert_eq!(
        a.additional_bytes,
        1,
        "raising a tenant from {held} to {} needs ONE new byte, not {}",
        held + 1,
        held + 1
    );
    assert_eq!(
        a.verdict,
        CapacityVerdict::CapacityAvailable as i32,
        "{}",
        a.reason
    );
    assert!(
        a.reason.contains("team"),
        "the sentence names the tenant: {}",
        a.reason
    );

    // And with no namespace the SAME cap is the harder question: all of it is
    // new. If the delta were ignored these two would agree, and they must not.
    let anonymous = ask(&plane, held + 1, "");
    assert_eq!(anonymous.held_bytes, 0);
    assert_eq!(anonymous.additional_bytes, held + 1);
    assert_ne!(
        anonymous.additional_bytes, a.additional_bytes,
        "naming the namespace must change the arithmetic, or `held_bytes` is decoration"
    );

    // A namespace the catalog does not hold is NOT_FOUND, never a silent zero:
    // quoting a raise against the wrong tenant's zero is how a seller
    // oversells.
    let err = plane
        .capacity(&CapacityRequest {
            requested_bytes: 1,
            namespace: "no-such-tenant".into(),
            disks: false,
        })
        .expect_err("an unknown namespace must not be treated as holding nothing");
    assert!(matches!(err, ControlError::NotFound(_)), "{err:?}");
}

/// On this box gunnar is not PID 1, so the answer must NOT claim the set is
/// sealed. The sealed arm is driven over recorded appliance facts in
/// `capacity.rs`'s unit tests — this asserts the live reader does not fake it.
#[test]
fn the_growth_answer_on_this_developer_box_is_never_sealed() {
    let rig = Rig::new();
    let plane = rig.plane(DiskFloor {
        percent: 0,
        bytes: 1,
    });
    let a = ask(&plane, 1, "");
    assert_ne!(
        a.growth,
        SetGrowth::GrowthSealed as i32,
        "gunnar is not PID 1 here ({}), so nothing may report a sealed appliance: {}",
        std::process::id(),
        a.reason
    );
    assert_ne!(
        a.growth,
        SetGrowth::Unspecified as i32,
        "every answer carries a growth verdict; UNSPECIFIED is a field nobody filled"
    );
    assert!(
        !a.reason.contains("UNUSED AND INVISIBLE"),
        "and must not tell an operator on a shelled box that a disk is wasted: {}",
        a.reason
    );
}

/// **A plane with no disk gate declines rather than quoting a floor it is not
/// enforcing.** Zero bytes free would read as a full disk; a default floor
/// would read as a promise this server never made.
#[test]
fn a_plane_with_no_disk_gate_declines_instead_of_quoting_a_floor_it_does_not_enforce() {
    let rig = Rig::new();
    let plane = CoreControlPlane::new(Arc::clone(&rig.stores));
    let err = plane
        .capacity(&CapacityRequest {
            requested_bytes: 1,
            namespace: String::new(),
            disks: false,
        })
        .expect_err("no gate wired, no quote");
    let ControlError::Unimplemented(line) = err else {
        panic!("Unimplemented, not {err:?}");
    };
    assert!(line.contains("--disk-floor"), "{line}");
}

// ───────────────────────────────────────────────────────────────────────────
// The box: cores and RAM are measured off THIS machine, absent when not
// ───────────────────────────────────────────────────────────────────────────

/// `/proc/meminfo`, read and parsed HERE — not through `probe::parse_meminfo`,
/// so the comparison is between two readers and not one reader twice.
fn meminfo_here() -> (u64, u64) {
    let text = std::fs::read_to_string("/proc/meminfo").expect("/proc/meminfo on a Linux host");
    let kib = |key: &str| -> u64 {
        text.lines()
            .find(|l| l.starts_with(key))
            .and_then(|l| l.split_whitespace().nth(1))
            .and_then(|n| n.parse::<u64>().ok())
            .unwrap_or_else(|| panic!("{key} in /proc/meminfo"))
    };
    (kib("MemTotal:") * 1024, kib("MemAvailable:") * 1024)
}

/// This process's cgroup v2 `memory.max`, read HERE; `None` when unlimited.
fn cgroup_memory_max_here() -> Option<u64> {
    let own = std::fs::read_to_string("/proc/self/cgroup").ok()?;
    let rel = own
        .lines()
        .find_map(|l| l.strip_prefix("0::"))?
        .trim()
        .trim_start_matches('/');
    let max = std::fs::read_to_string(format!("/sys/fs/cgroup/{rel}/memory.max")).ok()?;
    max.trim().parse().ok()
}

/// The affinity mask's popcount out of `/proc/self/status`, counted HERE, and
/// the cgroup v2 `cpu.max` quota in whole CPUs when one is set.
fn cpus_here() -> (usize, Option<usize>) {
    let status = std::fs::read_to_string("/proc/self/status").expect("/proc/self/status");
    let list = status
        .lines()
        .find_map(|l| l.strip_prefix("Cpus_allowed_list:"))
        .expect("Cpus_allowed_list")
        .trim();
    let count = |list: &str| {
        let mut n = 0usize;
        for range in list.split(',') {
            n += match range.split_once('-') {
                Some((lo, hi)) => hi.parse::<usize>().unwrap() - lo.parse::<usize>().unwrap() + 1,
                None => 1,
            };
        }
        n
    };
    // The kernel's mask may name every POSSIBLE CPU (0-127 on a 32-CPU host):
    // what a task can run on is that mask ∩ the ONLINE CPUs.
    let online = std::fs::read_to_string("/sys/devices/system/cpu/online")
        .expect("/sys/devices/system/cpu/online");
    let allowed = count(list).min(count(online.trim()));
    let quota = (|| {
        let own = std::fs::read_to_string("/proc/self/cgroup").ok()?;
        let rel = own
            .lines()
            .find_map(|l| l.strip_prefix("0::"))?
            .trim()
            .trim_start_matches('/');
        let raw = std::fs::read_to_string(format!("/sys/fs/cgroup/{rel}/cpu.max")).ok()?;
        let mut parts = raw.split_whitespace();
        let quota: f64 = parts.next()?.parse().ok()?;
        let period: f64 = parts.next()?.parse().ok()?;
        Some((quota / period).ceil().max(1.0) as usize)
    })();
    (allowed, quota)
}

/// **The RAM on the wire is `/proc/meminfo`'s, read independently, and not a
/// constant.** `MemTotal` must match to the byte (it does not move); `MemAvailable`
/// must be present, under the total, and within a moment's drift of a second
/// read — the tolerance is there because a live box moves between two reads,
/// and it is asserted because an absent or zero value must not pass.
#[test]
fn the_ram_fields_are_this_hosts_meminfo_read_by_a_second_reader() {
    let rig = Rig::new();
    let plane = rig.plane(DiskFloor {
        percent: 0,
        bytes: 1,
    });
    let a = ask(&plane, 1, "");
    let (mem_total, mem_available) = meminfo_here();
    let expected_total = match cgroup_memory_max_here() {
        Some(cap) if cap < mem_total => cap,
        _ => mem_total,
    };
    assert_eq!(
        a.ram_bytes_total,
        Some(expected_total),
        "ram_bytes_total must be what /proc/meminfo says on this host (MemTotal {mem_total}, \
         cgroup cap {:?}): {}",
        cgroup_memory_max_here(),
        a.machine_reason
    );
    let available = a
        .ram_bytes_available
        .expect("MemAvailable is readable on this host");
    assert!(
        available > 0 && available <= expected_total,
        "{available} of {expected_total}"
    );
    let drift = available.abs_diff(mem_available.min(expected_total));
    assert!(
        drift < expected_total / 5,
        "MemAvailable on the wire ({available}) is not within a fifth of an independent read \
         ({mem_available}) — that is not two readings of one file"
    );
    assert!(
        a.machine_reason.contains("/proc/meminfo"),
        "{}",
        a.machine_reason
    );
}

/// **The cores on the wire are the affinity mask's popcount** (∩ the cgroup
/// quota when one is set), read here from `/proc/self/status`, ×1000.
#[test]
fn the_cores_on_the_wire_are_the_affinity_masks_popcount_in_millicores() {
    let rig = Rig::new();
    let plane = rig.plane(DiskFloor {
        percent: 0,
        bytes: 1,
    });
    let a = ask(&plane, 1, "");
    let (allowed, quota) = cpus_here();
    let expected = quota.map_or(allowed, |q| q.min(allowed));
    assert_eq!(
        a.cpu_millicores_total,
        Some(expected as u64 * 1000),
        "Cpus_allowed_list counts {allowed} CPUs, cpu.max quota {quota:?}: {}",
        a.machine_reason
    );
    assert!(expected >= 1, "a Linux box has a CPU");
    // The busy figure is the 1-minute load ×1000: present on this host, and
    // the reason line owns up to it being an approximation.
    let busy = a
        .cpu_millicores_busy
        .expect("/proc/loadavg is readable on this host");
    let load1: f64 = std::fs::read_to_string("/proc/loadavg")
        .expect("loadavg")
        .split_whitespace()
        .next()
        .unwrap()
        .parse()
        .unwrap();
    assert!(
        (busy as f64 - load1 * 1000.0).abs() < 1000.0 + load1 * 250.0,
        "busy {busy} vs an independent load read of {load1}"
    );
    assert!(
        a.machine_reason.contains("APPROXIMATION"),
        "{}",
        a.machine_reason
    );
}

/// `CapacityResponse` **as an older client compiled it**: the thirteen disk
/// fields and nothing after them. Decoding today's bytes into this is exactly
/// what a monetize built before the box fields does.
#[derive(Clone, PartialEq, prost::Message)]
struct OlderCapacityResponse {
    #[prost(enumeration = "CapacityVerdict", tag = "1")]
    verdict: i32,
    #[prost(enumeration = "SetGrowth", tag = "2")]
    growth: i32,
    #[prost(string, tag = "3")]
    root: String,
    #[prost(uint64, tag = "4")]
    total_bytes: u64,
    #[prost(uint64, tag = "5")]
    free_bytes: u64,
    #[prost(uint64, tag = "6")]
    floor_bytes: u64,
    #[prost(uint64, tag = "7")]
    servable_bytes: u64,
    #[prost(uint64, tag = "8")]
    requested_bytes: u64,
    #[prost(uint64, tag = "9")]
    held_bytes: u64,
    #[prost(uint64, tag = "10")]
    additional_bytes: u64,
    #[prost(uint64, tag = "11")]
    shortfall_bytes: u64,
    #[prost(string, tag = "12")]
    data_set: String,
    #[prost(string, tag = "13")]
    reason: String,
}

/// **An older client gets the same disk answer, byte for byte.** Today's
/// response, encoded, is decoded by the message as it was before the box
/// fields existed; every one of its thirteen fields equals today's, and the
/// bytes the old client emits when it re-encodes are exactly today's bytes
/// with the box fields stripped — so the addition cost that client nothing.
#[test]
fn an_older_client_decodes_the_same_disk_answer_from_todays_bytes() {
    let rig = Rig::new();
    let plane = rig.plane(DiskFloor {
        percent: 0,
        bytes: 1,
    });
    let today = ask(&plane, 1, "");
    assert!(
        today.ram_bytes_total.is_some() && today.cpu_millicores_total.is_some(),
        "this test needs the new fields PRESENT, or it proves nothing: {}",
        today.machine_reason
    );
    let bytes = today.encode_to_vec();
    let older = OlderCapacityResponse::decode(bytes.as_slice()).expect("an older client decodes");
    assert_eq!(
        (
            older.verdict,
            older.growth,
            &older.root,
            older.total_bytes,
            older.free_bytes,
            older.floor_bytes,
            older.servable_bytes
        ),
        (
            today.verdict,
            today.growth,
            &today.root,
            today.total_bytes,
            today.free_bytes,
            today.floor_bytes,
            today.servable_bytes
        )
    );
    assert_eq!(
        (
            older.requested_bytes,
            older.held_bytes,
            older.additional_bytes,
            older.shortfall_bytes,
            &older.data_set,
            &older.reason
        ),
        (
            today.requested_bytes,
            today.held_bytes,
            today.additional_bytes,
            today.shortfall_bytes,
            &today.data_set,
            &today.reason
        )
    );
    // Byte-identical: today's message with EVERY field the older client does not know cleared
    // encodes to exactly what the older client re-encodes — the disk half of the wire did not
    // move. `OlderCapacityResponse` stops at tag 13, so everything above 13 is cleared here.
    //
    // 2026-09-16, lane AUDIT-TWIN: `grow_state` (19) and `hotplug` (20) were added after this
    // list was written and not added to it, so this assertion held only on a machine that
    // measured both as their default. It was RED on this box for `hotplug`, which
    // `dashboard::probe::pci_hotplug` reads true on any kernel that has the ACPI/PCIe hot-plug
    // driver — so the test proved the wire had not moved only where the box happened to be
    // boring. A cleared-field list has to be the complement of the older message's tags, not a
    // list of the fields somebody remembered.
    let stripped = gunnar_proto::CapacityResponse {
        cpu_millicores_total: None,
        ram_bytes_total: None,
        ram_bytes_available: None,
        cpu_millicores_busy: None,
        machine_reason: String::new(),
        grow_state: None,
        hotplug: false,
        // 21-26: the disk test (T6, agent/weakest-is-twin), added after this list and
        // not to it — the same miss the note above records for 19 and 20.
        disk_test: 0,
        disk_seq_write_bps: None,
        disk_seq_read_bps: None,
        disk_sync_milliops_per_s: None,
        disk_read_how: String::new(),
        disk_reason: String::new(),
        ..today.clone()
    };
    assert_eq!(stripped.encode_to_vec(), older.encode_to_vec());
    assert!(
        bytes.len() > older.encoded_len(),
        "today's bytes carry more than the older message — else the new fields were absent"
    );
}

/// **The same RPC, not a new one.** The box facts ride on `Admin.Capacity`;
/// `gunnar-grpc`'s `METHODS` census gains nothing — the `Admin.*` family is
/// exactly what it was.
#[test]
fn the_box_facts_ride_on_admin_capacity_and_add_no_rpc_to_the_census() {
    let admin: Vec<&str> = gunnar_grpc::auth::METHODS
        .iter()
        .copied()
        .filter(|m| m.starts_with("Admin."))
        .collect();
    // The three `Grow*` verbs are the runtime data-set growth (2026-09-14,
    // `DATA-SET-GROWTH-FLOW.md` §3) — a deliberate addition with its own
    // census entries and sweep rows, not the box facts sprouting an RPC.
    //
    // `Admin.IssuerInfo` is the same kind of addition: `2880a16` ("pki read: a
    // running appliance can be asked which CA it mints with"), which added the
    // verb and the census entry and did NOT add the row here. This gate has
    // been red on master ever since that merge landed, which is how a gate that
    // names a whole family fails — silently, on somebody else's branch, with a
    // message about box facts that has nothing to do with the change.
    assert_eq!(
        admin,
        [
            // `Admin.AuditTrail` (lane T11, 2026-09-21) is the third addition of the
            // same kind: a read that lets a RESTORED box be asked whether its audit
            // trail came back. Named here on the branch that added it, rather than
            // left to redden somebody else's, which is the mistake this comment
            // records `Admin.IssuerInfo` having made.
            "Admin.AuditTrail",
            "Admin.Capacity",
            "Admin.GrowResume",
            "Admin.GrowStart",
            "Admin.GrowStatus",
            "Admin.Health",
            "Admin.IssuerInfo",
            // `Admin.SetTunnelAddress` (T17, the WireGuard address of an enrolled box) and
            // `Admin.IssueTwinCertificate` (T9, the twin's pairing) — each a verb of its
            // own with its own census entry and sweep row, in `METHODS` order.
            "Admin.SetTunnelAddress",
            "Admin.IssueTwinCertificate",
            "Admin.Stats",
            "Admin.StorageInfo",
            // `Admin.Topology` (2026-10-04): the estate's boxes for the console's
            // Machine card — a read of its own, not a box fact.
            "Admin.Topology",
            "Admin.VerifyChain",
            // `Admin.GetSetting` / `Admin.SetSetting` (owner 2026-10-26): the generic server
            // settings a console keeps its switches in — verbs of their own, not box facts.
            "Admin.GetSetting",
            "Admin.SetSetting",
        ],
        "the Admin family changed; the box facts were meant to ride on Admin.Capacity"
    );
    assert_eq!(
        gunnar_grpc::auth::METHODS
            .iter()
            .filter(|m| **m == "Admin.Capacity")
            .count(),
        1
    );
}

/// **Every disk, only when asked** (`CapacityRequest.disks`, 2026-09-28, the
/// console's admin disk view). A quote (`disks: false`) carries no disk report
/// and so pays for no mount-table walk; the admin read (`disks: true`) gets
/// every storage mount with the store root placed on its mount under the label
/// `data`, measured by the same statvfs the data-set figures are — so the two
/// agree on the total — and the `--disk-floor` in force on it.
#[test]
fn every_disk_is_reported_only_when_asked_and_the_data_root_is_placed_on_its_mount() {
    let rig = Rig::new();
    let floor = DiskFloor {
        percent: 5,
        bytes: 2 * MIB,
    };
    let plane = rig.plane(floor);

    assert!(
        ask(&plane, 1, "").disks.is_none(),
        "a quote paid for the disk report"
    );

    let answer = plane
        .capacity(&CapacityRequest {
            requested_bytes: 0,
            namespace: String::new(),
            disks: true,
        })
        .expect("a plane with a disk gate answers");
    let disks = answer.disks.as_ref().expect("asked for, so reported");
    assert!(disks.problems.is_empty(), "{:?}", disks.problems);
    assert!(disks.measured_at_unix_ms > 0);
    let data = disks
        .mounts
        .iter()
        .find(|m| m.labels.iter().any(|l| l == "data"))
        .unwrap_or_else(|| panic!("the store root is on no reported mount: {:?}", disks.mounts));
    assert!(data.measured, "{data:?}");
    assert_eq!(
        data.total_bytes, answer.total_bytes,
        "two statvfs of one filesystem disagree on its size"
    );
    let expected_floor = (data.total_bytes / 100 * 5)
        .max(2 * MIB)
        .min(data.total_bytes);
    assert!(
        data.floor_bytes.abs_diff(expected_floor) <= data.total_bytes / 1000 + 1,
        "the floor on the data mount is not the --disk-floor in force: {} vs {expected_floor}",
        data.floor_bytes
    );
    assert!(
        disks
            .mounts
            .iter()
            .any(|m| m.labels.iter().any(|l| l == "system")),
        "`/` is not labelled system: {:?}",
        disks.mounts
    );
    // The wire round-trips (an older decoder ignores field 27 entirely).
    let bytes = answer.encode_to_vec();
    let back = gunnar_proto::CapacityResponse::decode(bytes.as_slice()).expect("decodes");
    assert_eq!(back.disks.as_ref(), Some(disks));
}