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
//! **An entry larger than one call is restored — resumed INSIDE it** (restore-large-entry,
//! 2026-10-07).
//!
//! MEASURED by the r5 integration test (row R1-recharge-main-lost): a new main recharging from
//! its twin through monetize's restore lane restored up to a 200 MiB pack and then, every call,
//! "Bootstrap.Restore refused: the stream ended inside an entry; resend from the box's head + 1"
//! — 41 times, never progressing. Every stream the lane opens is cut a fixed time after it opens,
//! and the lane resumed per ENTRY: one entry that needs longer than a call to stream could never
//! be restored, and the main never came back.
//!
//! Here two real boxes — a twin that followed a primary, and a fresh main — are served on the
//! real wire, and a LANE drives the recharge the way monetize's gunnar guest does: per call, read
//! `Bootstrap.Status`, fetch the twin's log from where the box is (`Mirror.LogFetch`, from the
//! byte of the entry it holds part of when it says one), push it into `Bootstrap.Restore`, and
//! stop when the call's budget is spent — a byte budget here, so the test is deterministic: the
//! guest's is time. The big entry needs several calls; the lane must get through it.

use std::net::SocketAddr;
use std::path::Path;
use std::sync::Arc;
use std::time::{Duration, Instant};

use gix_object::Kind as ObjKind;
use gunnar_core::{HashKind, NewRepo, Principal, PrincipalKind, RepoRef};
use gunnar_grpc::{EventBus, ServerConfig, ServiceContext};
use gunnar_proto::bootstrap_client::BootstrapClient;
use gunnar_proto::mirror_client::MirrorClient;
use gunnar_server::stores::ServerStores;
use gunnar_server::twin::{self, Follower, Progress};
use gunnar_server::CoreControlPlane;
use gunnar_store::conformance::pack::PackBuilder;

/// The big entry: a pack holding one blob of noise this large (noise, so the pack is as large).
const BIG: usize = 24 << 20;
/// What one call of the lane carries: a third of the big entry and a bit — it is cut inside it
/// at least three times.
const BUDGET: u64 = 7 << 20;
/// Where a restoring box stages the entry it holds part of.
const PARTIAL_DIR: &str = "txlog-restore-partial";

fn free_addr() -> SocketAddr {
    let l = std::net::TcpListener::bind("127.0.0.1:0").expect("bind");
    l.local_addr().expect("addr")
}

/// A control plane for `stores`, served at `addr` until killed.
struct Served {
    _rt: tokio::runtime::Runtime,
    stop: Option<tokio::sync::oneshot::Sender<()>>,
    addr: SocketAddr,
}

fn serve(stores: &Arc<ServerStores>) -> Served {
    let addr = free_addr();
    let plane: Arc<dyn gunnar_grpc::ControlPlane> =
        Arc::new(CoreControlPlane::new(Arc::clone(stores)));
    let ctx = ServiceContext::new(
        plane,
        Arc::new(EventBus::new(16)),
        ServerConfig::default().insecure_allow_all_for_local_development(),
    );
    let rt = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(2)
        .enable_all()
        .build()
        .expect("rt");
    let (tx, rx) = tokio::sync::oneshot::channel::<()>();
    rt.spawn(async move {
        let _ = gunnar_grpc::router(ctx, || 0)
            .serve_with_shutdown(addr, async {
                let _ = rx.await;
            })
            .await;
    });
    wait("the box listens", Duration::from_secs(10), || {
        std::net::TcpStream::connect(addr).ok()
    });
    Served {
        _rt: rt,
        stop: Some(tx),
        addr,
    }
}

impl Served {
    fn kill(mut self) {
        if let Some(tx) = self.stop.take() {
            let _ = tx.send(());
        }
        let addr = self.addr;
        wait("the box stops listening", Duration::from_secs(10), || {
            std::net::TcpStream::connect(addr).is_err().then_some(())
        });
    }
}

fn wait<T>(what: &str, within: Duration, mut probe: impl FnMut() -> Option<T>) -> T {
    let t0 = Instant::now();
    loop {
        if let Some(v) = probe() {
            return v;
        }
        assert!(t0.elapsed() < within, "{what}: not within {within:?}");
        std::thread::sleep(Duration::from_millis(25));
    }
}

fn noise(seed: u64, n: usize) -> Vec<u8> {
    let mut x = seed
        .wrapping_mul(6364136223846793005)
        .wrapping_add(1442695040888963407);
    (0..n)
        .map(|_| {
            x = x
                .wrapping_mul(6364136223846793005)
                .wrapping_add(1442695040888963407);
            (x >> 33) as u8
        })
        .collect()
}

fn one_blob_pack(hash: gix_hash::Kind, content: &[u8]) -> (Vec<u8>, Vec<u8>) {
    let id = gix_object::compute_hash(hash, ObjKind::Blob, content).expect("hash");
    let mut pb = PackBuilder::new(hash);
    pb.object(ObjKind::Blob, content.to_vec());
    let fixture = pb.build().expect("pack");
    (fixture.bytes, id.as_bytes().to_vec())
}

/// What the primary holds: small entries on both sides of ONE big pack.
struct Primary {
    stores: Arc<ServerStores>,
    oids: Vec<Vec<u8>>,
}

fn primary(dir: &Path) -> Primary {
    let stores = Arc::new(ServerStores::open(dir).expect("primary stores"));
    stores.catalog().create_namespace("ns").expect("namespace");
    let repo = stores
        .create_repo(
            NewRepo::new(RepoRef::new("ns", "app").unwrap()).hash_kind(HashKind::Sha256),
            false,
        )
        .expect("repo");
    let store = stores.repo_store(&repo).expect("store");
    let git = store.serve();
    let hash = store.hash_kind();
    let (p1, o1) = one_blob_pack(hash, b"before the big one\n");
    let (p2, o2) = one_blob_pack(hash, &noise(7, BIG));
    let (p3, o3) = one_blob_pack(hash, b"after the big one\n");
    git.put_pack(&p1).expect("pack 1");
    git.update_ref("refs/heads/main", None, Some(&o1))
        .expect("main");
    git.put_pack(&p2).expect("the big pack");
    git.update_ref("refs/heads/big", None, Some(&o2))
        .expect("big");
    git.put_pack(&p3).expect("pack 3");
    git.update_ref("refs/heads/after", None, Some(&o3))
        .expect("after");
    stores
        .auth()
        .add_principal(Principal::new("alice", "Alice", PrincipalKind::User).unwrap())
        .expect("principal");
    assert!(
        p2.len() >= BIG,
        "the big pack is as big as its noise: {}",
        p2.len()
    );
    Primary {
        stores,
        oids: vec![o1, o2, o3],
    }
}

/// A twin of `primary` — a fresh box made a twin, following it until level — in `dir`.
fn twin_of(primary: &Primary, dir: &Path) -> Arc<ServerStores> {
    let twin = Arc::new(ServerStores::open(dir).expect("twin stores"));
    twin::become_twin(&twin, "primary.test:50051").expect("a fresh box becomes a twin");
    drop(twin);
    let twin = Arc::new(ServerStores::open(dir).expect("the twin, reopened as one"));
    // What a twin over the wire is told on its first poll (an in-process source carries no
    // identity): a box restored from it is restored from a NAMED history, as a network
    // restore requires.
    let id = twin::log_id(primary.stores.root()).expect("a primary mints its log's identity");
    twin::pin_log_id(twin.root(), &id).expect("the twin pins its primary's log identity");
    let f = Follower::new(
        Arc::clone(&twin),
        Box::new(Arc::clone(primary.stores.txlog())),
    );
    let head = primary.stores.txlog().head_seq();
    for _ in 0..64 {
        match f.step().expect("the twin follows") {
            Progress::CaughtUp { .. } => break,
            Progress::Applied { .. } => continue,
            other => panic!("the twin cannot follow: {other:?}"),
        }
    }
    assert_eq!(
        twin.txlog().head_seq(),
        head,
        "the twin is level with the primary"
    );
    twin
}

/// The sequence of the big pack's frame in a log.
fn big_seq(log: &gunnar_core::txlog::Log) -> u64 {
    (log.first_seq()..=log.head_seq())
        .find(|s| log.read(*s).map(|e| e.blob.len() >= BIG).unwrap_or(false))
        .expect("the big frame")
}

/// How a call of the lane ends when its budget is spent.
#[derive(Clone, Copy, Debug)]
enum Cut {
    /// The guest's own budget: it closes the request half at a piece boundary.
    Close,
    /// The host's deadline: the call is CANCELLED (tonic reads it as a clean end, or an error).
    Abort,
    /// An old twin: no `from_offset` asked or honoured, no `offset` on the chunks; closed at
    /// the budget.
    OldTwinClose,
}

/// What one call did.
#[derive(Debug)]
enum Call {
    /// The box answered the close: restored to this head.
    Done(u64),
    /// The budget ended the call; what the box said, if it said anything.
    Stopped(String),
}

/// **The lane**: one call = what the gunnar guest does in one call, over the real wire.
struct Lane {
    rt: tokio::runtime::Runtime,
    twin: SocketAddr,
    budget: u64,
}

impl Lane {
    fn new(twin: SocketAddr, budget: u64) -> Lane {
        Lane {
            rt: tokio::runtime::Builder::new_multi_thread()
                .worker_threads(2)
                .enable_all()
                .build()
                .unwrap(),
            twin,
            budget,
        }
    }

    fn status(&self, main: SocketAddr) -> gunnar_proto::BootstrapStatusResponse {
        self.rt.block_on(async {
            let mut c = BootstrapClient::connect(format!("http://{main}"))
                .await
                .expect("connect");
            c.status(gunnar_proto::BootstrapStatusRequest {})
                .await
                .expect("Bootstrap.Status")
                .into_inner()
        })
    }

    fn call(&self, main: SocketAddr, cut: Cut) -> Call {
        let budget = self.budget;
        let twin = self.twin;
        let st = self.status(main);
        self.rt.block_on(async move {
            let mut mirror = MirrorClient::connect(format!("http://{twin}"))
                .await
                .expect("the twin");
            let head = mirror
                .log_head(gunnar_proto::LogHeadRequest::default())
                .await
                .expect("LogHead")
                .into_inner();
            let from = if st.restoring {
                head.first_seq.max(st.log_head_seq + 1)
            } else {
                head.first_seq.max(1)
            };
            let old_twin = matches!(cut, Cut::OldTwinClose);
            let offset = if st.restoring && st.restore_partial_seq == from && !old_twin {
                st.restore_partial_bytes
            } else {
                0
            };
            let (tx, rx) = tokio::sync::mpsc::channel::<gunnar_proto::LogChunk>(4);
            let restore = tokio::spawn(async move {
                let mut c = BootstrapClient::connect(format!("http://{main}"))
                    .await
                    .expect("connect main");
                c.restore(tokio_stream::wrappers::ReceiverStream::new(rx))
                    .await
                    .map(|r| r.into_inner())
            });
            let (mut cursor, mut sent, mut highest) = (from, 0u64, 0u64);
            while cursor <= head.head_seq {
                let req = gunnar_proto::LogFetchRequest {
                    from: cursor,
                    to: head.head_seq,
                    max_bytes: 0,
                    from_offset: if cursor == from { offset } else { 0 },
                };
                let mut s = mirror.log_fetch(req).await.expect("LogFetch").into_inner();
                while let Some(mut chunk) = s.message().await.expect("the twin's stream") {
                    chunk.restore_head = head.head_seq;
                    chunk.log_id.clone_from(&head.log_id);
                    if old_twin {
                        chunk.offset = None;
                        chunk.entry_crc = None;
                    }
                    let (seq, last) = (chunk.seq, chunk.last);
                    sent += chunk.blob.len() as u64;
                    highest = highest.max(seq);
                    if tx.send(chunk).await.is_err() {
                        break;
                    }
                    if sent >= budget && !(last && seq >= head.head_seq) {
                        return match cut {
                            Cut::Close | Cut::OldTwinClose => {
                                drop(tx);
                                match restore.await.expect("the restore task") {
                                    Ok(r) => {
                                        Call::Stopped(format!("answered OK at head {}", r.head_seq))
                                    }
                                    Err(status) => Call::Stopped(status.message().to_owned()),
                                }
                            }
                            Cut::Abort => {
                                restore.abort();
                                drop(tx);
                                Call::Stopped("cancelled".into())
                            }
                        };
                    }
                }
                assert!(highest >= cursor, "the twin answered nothing at {cursor}");
                cursor = highest + 1;
            }
            drop(tx);
            match restore.await.expect("the restore task") {
                Ok(r) => Call::Done(r.head_seq),
                Err(status) => Call::Stopped(status.message().to_owned()),
            }
        })
    }
}

/// Every frame of `restored` is `twin`'s, byte for byte.
fn assert_frames_equal(twin: &ServerStores, restored: &ServerStores) {
    let (t, r) = (twin.txlog(), restored.txlog());
    assert_eq!(
        r.head_seq(),
        t.head_seq(),
        "the restored main is at the twin's head"
    );
    for seq in t.first_seq()..=t.head_seq() {
        assert_eq!(
            r.read(seq).unwrap(),
            t.read(seq).unwrap(),
            "seq {seq} differs between the twin's log and the restored main's"
        );
    }
}

/// The restored main, restarted as the primary it was, holds the primary's objects and refs.
fn assert_same_objects(primary: &Primary, restored: &ServerStores) {
    let at = RepoRef::new("ns", "app").unwrap();
    let p = primary
        .stores
        .repo_store(&primary.stores.catalog().get(&at).unwrap().unwrap())
        .unwrap();
    let r = restored
        .repo_store(
            &restored
                .catalog()
                .get(&at)
                .unwrap()
                .expect("the repository came back"),
        )
        .unwrap();
    for oid in &primary.oids {
        assert!(
            r.serve().has(oid).unwrap(),
            "object {} missing on the restored main",
            hex::encode(oid)
        );
        assert_eq!(
            p.serve().read(oid).unwrap(),
            r.serve().read(oid).unwrap(),
            "object {} differs",
            hex::encode(oid)
        );
    }
    let mut refs_p: Vec<_> = p
        .serve()
        .refs()
        .unwrap()
        .into_iter()
        .map(|r| (r.name, r.oid))
        .collect();
    let mut refs_r: Vec<_> = r
        .serve()
        .refs()
        .unwrap()
        .into_iter()
        .map(|r| (r.name, r.oid))
        .collect();
    refs_p.sort();
    refs_r.sort();
    assert_eq!(refs_p, refs_r, "refs");
}

/// ★ **The defect, and its cure.** A 24 MiB entry, a lane that carries 7 MiB per call, calls that
/// END cleanly at their budget (the guest's own deadline): the entry is cut inside at least three
/// times, each call resumes it at the byte the box holds durably, and the restore finishes. The
/// staging goes the moment the entry is applied — nothing is left on the volume — and the
/// restored main is the twin's log frame for frame and the primary's objects and refs.
///
/// RED before the fix: the box dropped the half entry every call and the lane asked for it again
/// from byte 0 — 12 calls, never past the big entry.
#[test]
fn an_entry_larger_than_one_call_is_cut_three_times_and_resumed_inside_it() {
    let (pd, td, md) = (
        tempfile::tempdir().unwrap(),
        tempfile::tempdir().unwrap(),
        tempfile::tempdir().unwrap(),
    );
    let primary = primary(pd.path());
    let twin = twin_of(&primary, td.path());
    let big = big_seq(twin.txlog());
    let twin_served = serve(&twin);
    let main = Arc::new(ServerStores::open(md.path()).expect("a fresh main"));
    let main_served = serve(&main);
    let lane = Lane::new(twin_served.addr, BUDGET);

    let mut inside = Vec::new();
    let mut done = None;
    for call in 0..12 {
        let said = lane.call(main_served.addr, Cut::Close);
        let st = lane.status(main_served.addr);
        println!(
            "call {call}: {said:?} — head {} restoring {} partial {}:{}",
            st.log_head_seq, st.restoring, st.restore_partial_seq, st.restore_partial_bytes
        );
        if st.log_head_seq >= big {
            assert!(
                !md.path().join(PARTIAL_DIR).exists(),
                "call {call}: the big entry is applied and its staging is still on the volume"
            );
        } else if st.log_head_seq + 1 == big {
            inside.push(st.restore_partial_bytes);
        }
        if let Call::Done(head) = said {
            done = Some(head);
            break;
        }
    }
    assert_eq!(
        done,
        Some(twin.txlog().head_seq()),
        "the restore never finished; the box held, call after call, {inside:?} bytes of seq {big}"
    );
    assert!(
        inside.len() >= 3,
        "the big entry was cut inside {} time(s), not three: {inside:?}",
        inside.len()
    );
    assert!(
        inside.windows(2).all(|w| w[1] > w[0]),
        "every call went further INTO the entry: {inside:?}"
    );
    assert!(
        !md.path().join(PARTIAL_DIR).exists(),
        "nothing staged is left on the volume"
    );
    assert_frames_equal(&twin, &main);
    main_served.kill();
    drop(main);
    let restored = ServerStores::open(md.path()).expect("the restored main, restarted");
    assert_same_objects(&primary, &restored);
    twin_served.kill();
}

/// ★ **A main restarted in the middle of the entry resumes from what it made durable.** The
/// calls are CANCELLED at their budget (the host's deadline, not the guest's close); after two
/// of them the main goes down — and, as a crash would, its staged file carries bytes past what
/// its record counts. It comes back, says the same durable count it said before, and the lane
/// finishes the entry from there; the result is the twin's log and the primary's objects.
#[test]
fn a_main_restarted_inside_the_entry_resumes_from_what_it_made_durable() {
    let (pd, td, md) = (
        tempfile::tempdir().unwrap(),
        tempfile::tempdir().unwrap(),
        tempfile::tempdir().unwrap(),
    );
    let primary = primary(pd.path());
    let twin = twin_of(&primary, td.path());
    let big = big_seq(twin.txlog());
    let twin_served = serve(&twin);
    let lane = Lane::new(twin_served.addr, BUDGET);

    let mut main = Arc::new(ServerStores::open(md.path()).expect("a fresh main"));
    let mut main_served = serve(&main);
    let mut held = Vec::new();
    for _ in 0..2 {
        lane.call(main_served.addr, Cut::Abort);
        // The cancelled stream's handler commits as it ends; give it the moment it needs.
        let st = wait(
            "the cancelled stream's handler let go",
            Duration::from_secs(20),
            || {
                let st = lane.status(main_served.addr);
                (st.restore_partial_seq == 0
                    || st.restore_partial_bytes > held.last().copied().unwrap_or(0))
                .then_some(st)
            },
        );
        held.push(st.restore_partial_bytes);
    }
    let before = lane.status(main_served.addr);
    // The main goes down mid-entry...
    main_served.kill();
    drop(main);
    // ...and, as after a power cut, its staged file holds a tail nobody counted.
    let data = md.path().join(PARTIAL_DIR).join("entry.blob");
    if data.exists() {
        use std::io::Write as _;
        std::fs::OpenOptions::new()
            .append(true)
            .open(&data)
            .unwrap()
            .write_all(&noise(99, 12_345))
            .unwrap();
    }
    main = Arc::new(ServerStores::open(md.path()).expect("the main, back mid-restore"));
    main_served = serve(&main);
    let after = lane.status(main_served.addr);
    assert!(after.restoring, "the main comes back still restoring");
    assert_eq!(
        (after.restore_partial_seq, after.restore_partial_bytes),
        (before.restore_partial_seq, before.restore_partial_bytes),
        "it holds what it made durable — no more, no less"
    );

    let mut done = None;
    for _ in 0..12 {
        if let Call::Done(head) = lane.call(main_served.addr, Cut::Close) {
            done = Some(head);
            break;
        }
    }
    assert_eq!(
        done,
        Some(twin.txlog().head_seq()),
        "the restore never finished after the restart (held {held:?} of seq {big} before it)"
    );
    assert!(
        !md.path().join(PARTIAL_DIR).exists(),
        "nothing staged is left on the volume"
    );
    assert_frames_equal(&twin, &main);
    main_served.kill();
    drop(main);
    let restored = ServerStores::open(md.path()).expect("the restored main, restarted");
    assert_same_objects(&primary, &restored);
    twin_served.kill();
}

/// **An older twin cannot carry an entry that needs more than one call, and the box says so by
/// name** instead of asking for the same resend for ever. The old twin ignores `from_offset` and
/// sends the entry from byte 0 with no `offset` on its chunks; the box skips what it holds, which
/// makes the stream no shorter: once a stream of such a sender ends without one byte past what was
/// held, the refusal is `restore-twin-too-old` — "upgrade the twin" — and nothing held is lost.
#[test]
fn an_old_twin_that_cannot_resume_inside_an_entry_is_named_not_looped_on() {
    let (pd, td, md) = (
        tempfile::tempdir().unwrap(),
        tempfile::tempdir().unwrap(),
        tempfile::tempdir().unwrap(),
    );
    let primary = primary(pd.path());
    let twin = twin_of(&primary, td.path());
    let twin_served = serve(&twin);
    let main = Arc::new(ServerStores::open(md.path()).expect("a fresh main"));
    let main_served = serve(&main);
    let lane = Lane::new(twin_served.addr, BUDGET);

    let first = lane.call(main_served.addr, Cut::OldTwinClose);
    let mut held = lane.status(main_served.addr).restore_partial_bytes;
    assert!(
        held > 0,
        "the first call staged part of the big entry: {first:?}"
    );
    // Each resend from byte 0 ends where the last did (the budget is the same): at the latest
    // the second one gains nothing, and is named.
    let mut named = None;
    for _ in 0..3 {
        let Call::Stopped(said) = lane.call(main_served.addr, Cut::OldTwinClose) else {
            panic!("an old twin cannot finish the big entry")
        };
        let now = lane.status(main_served.addr).restore_partial_bytes;
        assert!(now >= held, "nothing held is lost: {now} < {held}");
        held = now;
        if said.contains("restore-twin-too-old") {
            named = Some(said);
            break;
        }
    }
    let said = named.expect("the box never named the old twin");
    assert!(
        said.contains("upgrade the twin"),
        "the box says what to do: {said}"
    );
    // The twin upgraded: the same box finishes from what it held.
    let mut done = None;
    for _ in 0..12 {
        if let Call::Done(head) = lane.call(main_served.addr, Cut::Close) {
            done = Some(head);
            break;
        }
    }
    assert_eq!(done, Some(twin.txlog().head_seq()));
    assert_frames_equal(&twin, &main);
    main_served.kill();
    twin_served.kill();
}

/// **Bytes put together across calls are applied only if they ARE the entry.** One staged byte
/// flips on the main's disk between two calls: the last piece's crc (the twin's own payload crc,
/// checked before it left) does not match what the box assembled, nothing is applied, the
/// staging is dropped, and the entry starts again from byte 0 — and then finishes, the twin's
/// log frame for frame.
#[test]
fn a_staged_entry_whose_bytes_rotted_between_calls_is_refused_by_its_crc_and_taken_again() {
    let (pd, td, md) = (
        tempfile::tempdir().unwrap(),
        tempfile::tempdir().unwrap(),
        tempfile::tempdir().unwrap(),
    );
    let primary = primary(pd.path());
    let twin = twin_of(&primary, td.path());
    let big = big_seq(twin.txlog());
    let twin_served = serve(&twin);
    let main = Arc::new(ServerStores::open(md.path()).expect("a fresh main"));
    let main_served = serve(&main);
    let lane = Lane::new(twin_served.addr, BUDGET);

    lane.call(main_served.addr, Cut::Close);
    let st = lane.status(main_served.addr);
    assert_eq!(
        st.restore_partial_seq, big,
        "the first call staged part of the big entry"
    );
    {
        use std::io::{Read as _, Seek as _, SeekFrom, Write as _};
        let mut f = std::fs::OpenOptions::new()
            .read(true)
            .write(true)
            .open(md.path().join(PARTIAL_DIR).join("entry.blob"))
            .unwrap();
        f.seek(SeekFrom::Start(1000)).unwrap();
        let mut b = [0u8; 1];
        f.read_exact(&mut b).unwrap();
        f.seek(SeekFrom::Start(1000)).unwrap();
        f.write_all(&[b[0] ^ 0x5A]).unwrap();
    }
    let mut refused = None;
    let mut done = None;
    for _ in 0..16 {
        match lane.call(main_served.addr, Cut::Close) {
            Call::Done(head) => {
                done = Some(head);
                break;
            }
            Call::Stopped(said) if said.contains("crc") => refused = Some(said),
            Call::Stopped(_) => {}
        }
    }
    let refused = refused.expect("the rotted entry was refused by its crc");
    assert!(refused.contains("starts again from byte 0"), "{refused}");
    assert_eq!(
        done,
        Some(twin.txlog().head_seq()),
        "taken again from byte 0, the entry finished"
    );
    assert_frames_equal(&twin, &main);
    main_served.kill();
    twin_served.kill();
}