gunnar-server 2.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
//! The gunnar git server: everything that used to live behind gunnar-cli's
//! `--features server`, now its own crate.
//!
//! Nothing in here may reach `gunnar-client` except the `client`-gated
//! [`mirror`] module (replication's send-pack). That edge is what would drag
//! gix's worktree, status and index machinery into an appliance image, and it
//! is asserted absent from a resolved dependency graph in
//! `gunnar-cli/tests/feature_split.rs`.
#![doc(html_logo_url = "https://gunnar.rs/download/gunnar.webp", html_favicon_url = "https://gunnar.rs/download/favicon.svg")]

/// **The allocator every gunnar that serves is built on.**
///
/// A `#[global_allocator]` is the *executable's* to choose — `bench`'s
/// `stages.rs` says so in its own header, and cargo enforces it: two of them in
/// one binary is a hard error. So this crate does not install one. It publishes
/// the *choice* as a type, and each serving binary spends one line on the
/// declaration:
///
/// ```ignore
/// #[global_allocator]
/// static ALLOC: gunnar_server::ServerAllocator = gunnar_server::SERVER_ALLOCATOR;
/// ```
///
/// That is one implementation applied to the heap with the grain of the language rather than
/// against it. The choice lives in exactly one place, so the appliance, the
/// `gunnar-solo`/`gunnar-service` builds and the bench's `git-store-serve`
/// cannot drift onto different allocators — and `bench`'s counting probes,
/// which must install a wrapper of their own, wrap *this* rather than `System`,
/// so `alloc_mib_*` describes the allocator that actually ships. Installing it
/// in the library instead looks tidier for exactly as long as it takes someone
/// to build a probe: it collided with `stage-probe`'s `CountingAlloc` on the
/// first compile.
///
/// The type is defined either way — `System` when the feature is off — so no
/// declaration site needs a `#[cfg]`.
///
/// # Why, with the numbers
///
/// znippy's resolver used to retain its whole read arena. Freeing it
/// (`znippy 0f14d9b`) took anon-after-seed from 4481 MB to 438 MB and
/// **cost 78% on clone CPU** — 1.47 s to 2.62 s on a 352 396-object clone.
/// The obvious reading is that the eviction forces re-resolution. It does
/// not; the clone path never calls the resolver. The retained arena was an
/// accidental *pre-warmed malloc pool* for the clone's own ~2 GB of
/// allocations, and giving it back put glibc's `malloc` on the critical path
/// for every clone thereafter. Bisected: a build that frees but does not
/// refcount reproduces the whole regression, so the cost is the not-leaking,
/// not the counting.
///
/// Four-way grid, one box, interleaved, quiet, steady state:
///
/// | resolver | allocator | clone CPU | anon after seed |
/// |---|---|---:|---:|
/// | leaking | glibc | 1.47 s | 4481 MB |
/// | freeing | glibc | 2.62 s | 438 MB |
/// | leaking | mimalloc | 1.18 s | 1767 MB |
/// | **freeing** | **mimalloc** | **1.17 s** | **701 MB** |
///
/// The last row beats the leaking build's best cell on *both* axes at once,
/// and with a competent allocator the two resolvers are within 0.01 s — the
/// regression was never in znippy. It is user-space allocator cost and not
/// page faults: system time is identical (0.14 s) and suppressing trim with
/// `M_TOP_PAD` removed 20 762 faults while moving CPU by 0.04 s.
///
/// Opt out with `--no-default-features`; see this crate's manifest for what
/// that buys and what the dependency budget does and does not actually forbid.
#[cfg(feature = "mimalloc")]
pub type ServerAllocator = mimalloc::MiMalloc;

/// The value to declare. See [`ServerAllocator`].
#[cfg(feature = "mimalloc")]
pub const SERVER_ALLOCATOR: ServerAllocator = mimalloc::MiMalloc;

/// `System`, because the `mimalloc` feature is off. See [`ServerAllocator`] for
/// what that costs — on the measured clone it is 2.2x the CPU.
#[cfg(not(feature = "mimalloc"))]
pub type ServerAllocator = std::alloc::System;

/// The value to declare. See [`ServerAllocator`].
#[cfg(not(feature = "mimalloc"))]
pub const SERVER_ALLOCATOR: ServerAllocator = std::alloc::System;

pub mod admin;
/// **The appliance identity ceremony** — load-or-mint the WireGuard keypair on
/// the durable data root, emit the ONE canonical identity line every product
/// in the fleet emits, and refuse (never re-mint) a corrupt one. A thin caller
/// of `wg_appliance_core::appliance`, shared with holger and korp. Gated on
/// `GUNNAR_APPLIANCE=1`, which the `[[build.installer]]` declaration carries.
///
/// Behind `wg-client` with [`wg`]: the ceremony mints a WIREGUARD keypair and
/// exists to serve the tunnel, so a binary built without the tunnel has
/// nothing to mint and no reason to link `wg_appliance_core`.
#[cfg(feature = "wg-client")]
pub mod appliance;
pub mod attest;
/// The LFS tee: an object is a transaction-log frame before it is published.
pub mod txlog_lfs;
/// The twin: follows the primary's transaction log, applies it, restores from it.
pub mod twin;
/// The twin's mutual-TLS credential: minted at pairing, never on the medium.
pub mod twin_tls;
pub mod tunnel_addr;
/// **The declared budgets** — concurrent transfers, open repository handles and
/// the git memory budget — derived from cores + `RLIMIT_NOFILE` + `MemAvailable`
/// and named in one INFO row at start. The replacement for the three emergent
/// ceilings of 2026-08-29 (`19`, `25`, `256`).
pub mod budget;
/// **`Admin.Capacity`: "if a tenant's cap rose to N bytes, could you serve
/// it?"** — the question monetize must ask before it buys a disk. Answers with
/// a verdict measured off the SAME `statvfs` the push door refuses under, and,
/// independently, whether this data set can ever grow at all: the appliance is
/// SEALED (an md set laid once at install, no runtime growth path, PID 1 with
/// no shell), so a disk bought for it is billed and invisible.
pub mod capacity;
pub mod control_plane;
/// **The runtime data-set growth** (`DATA-SET-GROWTH-FLOW.md` §3): the phase
/// machine over a `World` seam, the go-ahead, and — with `grow-runtime` — the
/// appliance world that links edda's engine.
pub mod grow;
/// The VGA-console status screen the appliance draws as PID 1 (header band +
/// rolling events off the log tap).
pub mod dashboard;

/// **What emptying an account means**, in one place: the four classes of
/// bytes a user owns and the walk that reclaims them.
pub mod account_purge;
/// The account audit logs' retention sweep (2026-10-05).
pub mod account_audit_sweep;
/// **One worker, one job at a time, FIFO, deduplicated by subject** — the
/// runner `gc_queue` and `purge_queue` are two kinds on, and the hook a third
/// kind plugs into without editing it.
pub mod job_queue;
/// **One gc at a time, for the whole box** — the queue behind `Repos.Gc`.
pub mod gc_queue;
/// **One account purge at a time, and the lockout always comes off.** The
/// `Accounts.Purge` queue; see its module docs for why it is its own line and
/// not the gc one.
pub mod purge_queue;
/// **One mirror sync at a time, for the whole box** — the third kind on the one
/// runner, and the queue behind `Mirror.Sync`.
pub mod mirror_queue;
/// **What a mirror sync does**: one `gunnar-wire` `fetch_pack` over one of two
/// transports, and one compare-and-swap that moves the references.
pub mod mirror_fetch;
/// **Smart HTTP for the fetch side**, as the `BufRead`/`Write` pair
/// `fetch_pack` takes. The adapter that turns a request/response protocol back
/// into a stream.
#[cfg(feature = "mirror-http")]
pub mod mirror_http;
pub mod grpc_gate;
/// The tree diff `Refs.Diff` serves. Its own module because it is an algorithm
/// with an opinion about what it will not compute, and that opinion needs
/// somewhere to be written down.
/// An encrypted repository's push, checked before a byte of it is kept (crypt-level2 item 4).
/// It lives in [`storage`] (it walks the pushed pack with the znippy engine's own reader, and
/// the engine is named there and nowhere else — xtask `Rule::EngineByName`); its path stays.
#[cfg(feature = "engine-znippy")]
pub use storage::encrypted_quarantine;
pub mod diff;
/// Whole-file blame: `verify`'s line→commit link 1, done once for every line of
/// a file. Reuses `verify`'s tree/blob and parent readers and the `diff`
/// engine (one implementation).
pub mod blame;
/// Latest-revision literal code search: walk the tree at a ref, scan text
/// blobs. Bounded by a file-size and a match ceiling; not a persistent index.
pub mod search;
/// A hand-built in-memory git for the `blame` and `search` tests, so their
/// assertions name oids the test chose rather than trusting the answer.
#[cfg(test)]
#[cfg(test)]
mod testgit;
/// A repository's first commit, written by the server (`Repos.Create{readme}`).
pub mod first_commit;
/// The start-up disk test the appliance runs on /data (seq write/read, 4 KiB
/// fsync ops) — one `disktest` row on the screen.
pub mod durable;
pub mod disktest;
/// The machine at a glance (`StorageReality.glance`, 2026-10-04): the Machine card's lines, decided here.
pub mod glance;
/// `Admin.Topology`'s facts from this server: the pairing feed, the front's principals, the plugin registration (2026-10-04).
pub mod topology;
/// **The metered appliance** (the metered plan): the paid state per
/// namespace — verified, logged, stored, and read by the front doors. Compiled
/// only with the `metered` feature; see `Cargo.toml` for the doctrine.
#[cfg(feature = "metered")]
pub mod entitlement;
/// **The key that signs an actor ticket**: the appliance's word that one human
/// may act for one tenant. Its own key, minted at runtime into the secret
/// store, signing raw Ed25519 because the verifier on the far side of the
/// monetize seam is `monetize_embed`'s and not SSH's. Metered only — the
/// community appliance carries no service that would spend one.
#[cfg(feature = "metered")]
pub mod actor_ticket;
/// The descriptor limit: raised once (PID 1 to a million, anyone else to its
/// hard limit), and the open-repository handle cache sized to fit under it.
/// The fix for the rig's "202nd repository" `EMFILE` (2026-08-29).
pub mod limits;
/// The artifact registry and the OCI images it holds.
pub mod oci;
/// Gunnar's end of PID 1. The jobs an operating system used to do for the
/// server (signals, reaping, the network, DHCP, the stop and the power-off)
/// are edda's `appliance-pid1`, shared with Holger; this is what Gunnar says
/// about itself to it (`GUNNAR_NET`, the host name, the narration tap, GN-99).
pub mod boot;
/// **The fifth** of those jobs, added 2026-09-23: saying where the boot is.
/// `boot::ship_boot_narration` ends at `end of hand-off` and the estate's twin
/// died in the silence after it; this is the narrator that speaks from there
/// on — a stage line at every boundary, a tick a minute, and a fatal fault that
/// sends a stable code, waits to be heard and then powers the box off cleanly.
pub mod narrate;
pub mod stop_record;
/// **The fourth** of those jobs, added 2026-09-14: the wall clock. The appliance
/// booted two hours fast and refused every genuine credential and every Stripe
/// webhook for it; UDP/123 is closed on that network, so "now" arrives in the
/// `Date` header of an HTTPS response, from three sources that must agree.
pub mod clock;
/// **The boot window**: whether the stores have finished their post-crash work,
/// and the retryable `starting` answer a client gets until they have. The
/// 0.6-0.7 s of `the repository is unavailable; try again` after a power cut.
pub mod readiness;
/// The registry's one rule: no artifact without valid, logged provenance.
pub mod registry;
/// The build runner: checkout, sandbox, artifacts, signed provenance.
pub mod runner;
/// The build sandbox: a network namespace of the build's own, unprivileged.
pub mod sandbox;
/// The counters over HTTP. Opt-in; binds nothing by default.
pub mod scrape;
pub mod serve;
/// **The layout config** (`/boot/korp/layout.conf`): what the installer
/// decided, written down and sealed by the thing that did the deciding. gunnar
/// is TOLD where its storage is and obeys; it never surveys disks, an absent
/// config is answered as absent rather than inferred, and an unsigned one is
/// never presented as verified.
pub mod layout_config;
/// **The slow disk** (`/serialdat`): the other spindle, and the other failure
/// domain. One code path whatever is underneath it, and a three-valued claim —
/// off-stripe, same-device, unverified — over the three copies that are
/// supposed to outlive losing a data disk.
pub mod slow;
/// How gunnar reaches a company's identity provider (company SSO).
#[cfg(feature = "sso-egress")]
pub mod sso_egress;
/// The znippy cold tier, wired where objects enter the server.
pub mod storage;
pub mod push_space;
pub mod stores;
/// **The usage ledger** (2026-10-06): every namespace's stock meters, re-measured
/// only where something changed — what `UsageBatch.ReadMany` answers from.
pub mod usage_ledger;
/// **Can this control plane be moved onto mutual TLS, and who does it cost?**
/// The one preflight `gunnar serve --grpc-tls-self` refuses on and
/// `gunnar issuer readiness` asks in advance.
pub mod tls_preflight;
pub mod transport;
/// `gunnar verify` — the unbroken chain, walked backwards from a line of source.
pub mod verify;
/// **WireGuard**, so the appliance has an address that outlives its DHCP lease.
/// USERSPACE and compiled in (`wg-appliance-core`/boringtun), so it needs no
/// `wireguard` kernel module and works whatever the packed kernel thinks about
/// `CONFIG_WIREGUARD` — only `/dev/net/tun`, which is builtin. The tunnel
/// address goes on through `appliance_pid1::net::apply`, the same netlink writer every other
/// address does. Off unless the data root carries a `wg0.conf`.
///
/// Behind `wg-client`. With the feature off `wg-appliance-core` leaves the
/// dependency graph and boringtun's crypto tail (x25519-dalek,
/// chacha20poly1305, blake2, ring) goes with it — which is what an appliance
/// customer who unticks "WireGuard" is buying.
#[cfg(feature = "wg-client")]
pub mod wg;
/// The pairing loop PID 1 runs itself (nordisk-pairing) — see the module.
#[cfg(feature = "wg-client")]
pub mod pairing;
/// The installer's feed at rest: sealed, owner-only (h-feed-at-rest).
#[cfg(feature = "wg-client")]
pub mod fed_at_rest;
/// The principals the installer feeds (`principal.<id>`), installed by the
/// primary itself right after its fed admin — see the module.
#[cfg(feature = "wg-client")]
pub mod fed_principals;
/// The installer's mTLS endpoint on this VM (edda nordisk-pairing 0.3): the
/// bootstrap listener, the boot log the installer collects, the fresh-install reset.
#[cfg(feature = "wg-client")]
pub mod installer_listener;
/// **Live update** (owner 2026-10-12): what this box runs, a new app staged, the graceful
/// re-exec, the confirm — generic primitives over korp-installer's `live`.
pub mod live_update;
/// DEV-NARRATE (Skidbladnir nordisk-narrate): this box's boot log, sealed to
/// the installer's key, over UDP before the installer's link exists. A DEV
/// build only — no release row names the feature.
#[cfg(feature = "dev-narrate")]
pub mod dev_narrate;
/// The estate values the installer's door feeds (monetize key, clock key and peer),
/// taken instead of the medium's — see the module.
pub mod feed;

/// Whether a smart-HTTP credential may arrive over cleartext. Re-exported
/// from `gunnar-http` so `gunnar-cli`'s `serve` dispatch can name it without a
/// direct `gunnar-http` dependency of its own.
#[cfg(feature = "http")]
pub use gunnar_http::Cleartext;

/// **Dispatch a server verb.** The one entry point both this crate's binary and
/// `gunnar-cli`'s `run()` route server commands through, so the wiring lives
/// exactly once (one implementation).
///
/// The trailing catch-all is there only so this compiles inside a `gunnar-solo`
/// build, where feature unification gives [`gunnar_common::cli::Command`] the
/// client/control variants too. It is never reached: a server-only binary has no
/// other variants, and `gunnar-cli` routes only the server ones here.
///
/// # It also closes the stores, and that is why the dispatch is wrapped
///
/// [`storage::shutdown`] runs on the way out, on the error path as well as the
/// success path. Being *the* server dispatch is what makes one call site enough:
/// `gunnar-server`'s `main` and every `gunnar-cli` binary's server verbs both
/// arrive here, so neither `main` has to remember. Both of those `main`s end in
/// `std::process::exit(1)` on error, which unwinds nothing and drops nothing —
/// hence a call, before the value is returned to them, rather than a guard.
///
/// See [`storage::shutdown`] and `storage::znippy_engine::shutdown` for what is
/// being released and what it costs not to.
pub fn run(
    data_root: std::path::PathBuf,
    command: gunnar_common::cli::Command,
) -> anyhow::Result<()> {
    let outcome = dispatch(data_root, command);
    storage::shutdown();
    outcome
}

fn dispatch(
    data_root: std::path::PathBuf,
    command: gunnar_common::cli::Command,
) -> anyhow::Result<()> {
    use gunnar_common::cli::Command;

    match command {
        Command::Serve(args) => {
            use std::sync::Arc;

            let stores = Arc::new(stores::ServerStores::open(&data_root)?);
            let wire = transport::WireConfig::from_serve_args(&args)?;
            // **The installer's feed** (`crate::feed`): the values the join door
            // fed, persisted by the pairing loop to `<data>/pairing/joined.toml`
            // on an earlier boot. Published for the clock (which reads it on
            // every sync) and resolved against the medium's monetize key below.
            let fed_at_start = crate::feed::load_at_start(&data_root);
            let fed_monetize = fed_at_start.as_ref().and_then(|f| f.monetize_pubkey.clone());
            #[cfg(not(feature = "metered"))]
            if let Some(key) = &fed_monetize {
                anyhow::bail!(
                    "REFUSED [pairing-feed-monetize-open-build] the installer's door fed monetize key {key}, \
                     but this gunnar-server was built without the `metered` feature and contains no \
                     entitlement code at all; it would serve every namespace Free. Build the \
                     `gunnar-server-metered` variant, or stop the front feeding `monetize_pubkey`."
                );
            }
            #[cfg(not(feature = "metered"))]
            crate::feed::record_monetize_in_force(None);
            // `--monetize-pubkey` on a build that cannot honour it is refused BY
            // NAME, the `GUNNAR_NET=:dhcp` rule (`appliance_pid1::net::NetConfig::parse`): a
            // box told to bill by a binary that cannot must stop, not boot Free.
            #[cfg(not(feature = "metered"))]
            if let Some(key) = &args.monetize_pubkey {
                anyhow::bail!(
                    "--monetize-pubkey {key} was given, but this gunnar-server was built without \
                     the `metered` feature and contains no entitlement code at all; it would \
                     serve every namespace Free. Build the `gunnar-server-metered` variant."
                );
            }
            // And the mirror of that refusal on the metered side: NO DEFAULT
            // URL. This used to read `.unwrap_or("https://gunnar.rs/billing")`,
            // which put the builder's own page into every other builder's
            // binary — a metered gunnar with a forgotten flag pointed its
            // tenants at Vetra. `load` refuses instead, by flag name; see
            // `MeteredConfig::load` for why a missing link is not tolerable.
            // The medium's key (`--monetize-pubkey`) against the door's fed one:
            // either alone is taken, both must agree (`feed::resolve_monetize`
            // refuses a disagreement by name). `--billing-url` with no key from
            // EITHER source is refused here — the rule clap's `requires` used
            // to make before the key could arrive from the door.
            #[cfg(feature = "metered")]
            let metered = {
                // `fed` (the constant medium, 2026-09-24 evening): the medium
                // carries no key and SAYS so — the door feeds it. Until it has
                // (the first boot, before the join) the server starts KEYLESS
                // and the key applies LIVE when it arrives (`feed::monetize_arrived`,
                // owner 2026-09-25: avoid restarts); a later start reads it from
                // joined.toml.
                let awaits_feed = args.monetize_pubkey.as_ref().is_some_and(|k| k.is_fed());
                let medium = match &args.monetize_pubkey {
                    Some(key) if key.is_fed() => None,
                    Some(key) => Some(entitlement::read_pubkey(key)?),
                    None => None,
                };
                let resolved = crate::feed::resolve_monetize(medium, fed_monetize.as_deref())
                    .map_err(|why| anyhow::anyhow!(why))?;
                if medium.is_none() && resolved.is_some() {
                    tracing::info!("monetize key: from the installer's feed (<data>/pairing/joined.toml); the medium carries none");
                }
                crate::feed::record_monetize_in_force(resolved);
                match resolved {
                    None if awaits_feed => {
                        tracing::warn!(
                            "monetize key: the medium says `{}` — the installer's door feeds it, and it has not yet \
                             (first boot, before the join). Enforcing NOTHING until it arrives; it then applies in \
                             place, with no restart",
                            gunnar_common::monetize_key::FED
                        );
                        let mut cfg = entitlement::MeteredConfig::awaiting_feed(args.billing_url.as_deref())?;
                        cfg.vital = args.monetize_vital;
                        Some(cfg)
                    }
                    Some(key) => {
                        let mut cfg = entitlement::MeteredConfig::load(
                            &gunnar_common::monetize_key::MonetizePubkey::Literal(key),
                            args.billing_url.as_deref(),
                        )?;
                        cfg.vital = args.monetize_vital;
                        Some(cfg)
                    }
                    None if args.billing_url.is_some() => anyhow::bail!(
                        "--billing-url (GUNNAR_BILLING_URL) was given, but no monetize key: neither \
                         --monetize-pubkey (GUNNAR_MONETIZE_PUBKEY) on the medium nor `monetize_pubkey` \
                         fed by the installer's door. A pay page with no key to verify facts with \
                         bills nobody."
                    ),
                    None => None,
                }
            };
            // The community plan gate's floor. Parsed here, once, so a typo is a
            // startup error with the flag's name in it and not a gate that
            // silently runs on the default.
            let disk_floor = gunnar_ssh::DiskFloor::parse(&args.disk_floor)
                .map_err(|err| anyhow::anyhow!(err))?;
            // The usage share's settings, parsed here for the same reason: a
            // typo in `--admit-limit` is a startup error naming the flag.
            #[cfg(feature = "admit-share")]
            let admit = admit_settings(args.admit_limit.as_deref(), args.admit_window_days)?;
            let config = serve::ServeConfig {
                ssh_bind: args.ssh_bind,
                disk_floor,
                grpc_bind: args.grpc_bind,
                grpc_bind_device: None,
                admin_key: args.admin_key.clone(),
                restore_pubkeys: args.restore_pubkeys.clone(),
                no_grpc: args.no_grpc,
                wg_only: args.wg_only,
                twin_role: args.role == gunnar_common::cli::ServeRole::Twin,
                allow_anonymous: !args.no_anonymous,
                anonymous: match args.anonymous {
                    gunnar_common::cli::AnonymousArg::Allow => gunnar_ssh::AnonymousPolicy::Allow,
                    gunnar_common::cli::AnonymousArg::Refuse => gunnar_ssh::AnonymousPolicy::Refuse {
                        browse_host: args.browse_host.clone(),
                    },
                },
                max_concurrent_transfers: args.max_concurrent_transfers,
                memory_budget_bytes: args.git_memory_bytes,
                admission_wait: args.admission_wait_ms.map(std::time::Duration::from_millis),
                max_transfers_per_principal: args.max_transfers_per_principal,
                max_anonymous_transfers: args.max_anonymous_transfers,
                #[cfg(feature = "admit-share")]
                admit,
                per_principal_limit_is_hard: args.hard_per_principal_limit,
                max_object_bytes: args.max_object_bytes,
                metrics_bind: args.metrics_bind,
                // I11's passive heartbeat. `map` and not a bare struct: the
                // three tuning flags carry `default_value_t`, so they are always
                // present, and it is `--watch-primary` alone that decides
                // whether a watchdog exists at all.
                #[cfg(feature = "replication")]
                standby_watch: args.watch_primary.map(|primary| {
                    crate::standby_watch::StandbyWatch {
                        primary,
                        probe_interval: std::time::Duration::from_millis(args.watch_probe_ms),
                        connect_timeout: std::time::Duration::from_millis(args.watch_connect_ms),
                        promote_after: args.watch_promote_after,
                    }
                }),
                // 5.4: the boot check runs on every box with replication (it only
                // SAYS unless `--repair-auto`); the pull channel only with `--fleet`.
                #[cfg(feature = "replication")]
                repair: Some(crate::repair::RepairConfig {
                    auto: args.repair_auto,
                    key: args.repair_key.clone(),
                    twin: args.repair_twin.clone(),
                    // The medium's flags, or the installer's feed (the front hands every box
                    // the ONE's door, M-D35); both and different refuses to start.
                    fleet: crate::repair::resolve_fleet(match (&args.fleet, &args.fleet_instance) {
                        (Some(url), Some(instance)) => Some(crate::repair::FleetConfig {
                            url: url.clone(),
                            ca: args.fleet_ca.clone(),
                            ca_pem: None,
                            at: None,
                            instance: instance.clone(),
                            role: args.fleet_role.clone().unwrap_or_default(),
                            monetize_pubkey: args.monetize_pubkey.as_ref().and_then(|k| k.literal()),
                            every: std::time::Duration::from_secs(args.fleet_every_s.max(1)),
                        }),
                        (Some(_), None) => anyhow::bail!("--fleet needs --fleet-instance (GUNNAR_FLEET_INSTANCE): the estate this box reports for"),
                        _ => None,
                    }, fed_at_start.as_ref(), std::time::Duration::from_secs(args.fleet_every_s.max(1)))
                    .map_err(|why| anyhow::anyhow!(why))?,
                }),
                #[cfg(feature = "http")]
                http_bind: args.http_bind,
                #[cfg(feature = "http")]
                http_cleartext: if args.http_allow_cleartext_credentials {
                    Cleartext::Allowed
                } else {
                    Cleartext::RequireProxyTls
                },
                #[cfg(feature = "http")]
                http_tls: if args.http_tls_self {
                    Some(serve::HttpTls::SelfMinted)
                } else if let (Some(cert), Some(key)) = (&args.http_tls_cert, &args.http_tls_key) {
                    Some(serve::HttpTls::Identity(gunnar_http::TlsIdentity {
                        cert_chain_pem: std::fs::read_to_string(cert)
                            .map_err(|e| anyhow::anyhow!("reading {}: {e}", cert.display()))?,
                        key_pem: std::fs::read_to_string(key)
                            .map_err(|e| anyhow::anyhow!("reading {}: {e}", key.display()))?,
                    }))
                } else {
                    None
                },
                grpc_tls: if args.grpc_tls_self {
                    Some(serve::GrpcTls::SelfMinted)
                } else {
                    None
                },
                web_relying_parties: args.web_relying_party.clone(),
                web_require_user_verification: args.web_require_user_verification,
                grpc_replication_bind: args.grpc_replication_bind,
                runner: args.runner.then(|| runner::WatchConfig {
                    // `requires_all` on the flag makes both present; the
                    // `expect` names which invariant is being leaned on rather
                    // than defaulting to a path nobody chose.
                    workspace: args
                        .runner_workspace
                        .clone()
                        .expect("--runner requires --runner-workspace"),
                    builder: args
                        .runner_builder
                        .clone()
                        .expect("--runner requires --runner-builder"),
                    allow_network: args.runner_allow_network,
                    refs: Vec::new(),
                    max_builds: None,
                }),
                wire,
                #[cfg(feature = "metered")]
                metered,
                ..serve::ServeConfig::default()
            };
            // The runtime is built by `serve` rather than with `#[tokio::main]`
            // so the blocking-pool size, which used to be the server's accidental
            // concurrency ceiling, is a named constant next to the limit
            // it has to stay above rather than a tokio default nobody chose.
            serve::runtime()?.block_on(serve::run(stores, config, args.check))
        }

        Command::Namespace(command) => {
            let stores = stores::ServerStores::open(&data_root)?;
            admin::namespace(&stores, command)
        }
        Command::Repo(command) => {
            // **`archive-out` must work while the server is running**, and
            // `ServerStores::open` takes redb's exclusive lock on
            // `control.redb` — a live server holds it, so opening the stores
            // here would make the one verb a backup needs the one verb a
            // serving box cannot run. It does not need them: an archive is
            // three files under `repos/<ns>/<name>.git` and the path resolver
            // is the same sandbox the SSH listener uses.
            #[cfg(feature = "engine-znippy")]
            if matches!(command, gunnar_common::cli::RepoCommand::ArchiveOut { .. }) {
                return admin::repo_archive_out_without_stores(&data_root, command);
            }
            let stores = stores::ServerStores::open(&data_root)?;
            admin::repo(&stores, command)
        }
        Command::Principal(command) => {
            let stores = stores::ServerStores::open(&data_root)?;
            admin::principal(&stores, command)
        }
        Command::Issuer(command) => {
            let stores = stores::ServerStores::open(&data_root)?;
            admin::issuer(&stores, command)
        }
        Command::Key(command) => {
            let stores = stores::ServerStores::open(&data_root)?;
            admin::key(&stores, command)
        }
        Command::Token(command) => {
            let stores = stores::ServerStores::open(&data_root)?;
            admin::token(&stores, command)
        }
        Command::Attest(command) => {
            let stores = stores::ServerStores::open(&data_root)?;
            admin::attest(&stores, command)
        }
        Command::Verify(args) => admin::verify(&data_root, args),
        Command::Blame(args) => admin::blame(&data_root, args),
        Command::Search(args) => admin::search(&data_root, args),
        Command::Build(command) => {
            let stores = stores::ServerStores::open(&data_root)?;
            admin::build(&stores, command)
        }
        Command::Forge(command) => {
            let stores = stores::ServerStores::open(&data_root)?;
            admin::forge(&stores, command)
        }
        Command::Registry(command) => {
            let stores = std::sync::Arc::new(stores::ServerStores::open(&data_root)?);
            admin::registry(&stores, command)
        }
        Command::Grant(args) => {
            let stores = stores::ServerStores::open(&data_root)?;
            admin::grant(&stores, args)
        }
        Command::Revoke(args) => {
            let stores = stores::ServerStores::open(&data_root)?;
            admin::revoke(&stores, args)
        }
        Command::Gc(args) => {
            let stores = stores::ServerStores::open(&data_root)?;
            admin::gc(&stores, args)
        }
        // No `ServerStores::open`: that opens `control.redb`, and redb takes an
        // exclusive file lock a running server already holds. Reading history
        // must touch nothing but the repository. (Until 2026-08-10 the lock this
        // named was the control *repository*'s; the sentence survives the
        // migration because the control database takes one of its own —
        // one process per store root — and this verb has to run against a live server.)
        Command::Reflog(args) => admin::reflog(&data_root, args),
        // No stores at all: minting a key touches nothing this server owns.
        #[cfg(feature = "wg-client")]
        Command::Wg(command) => wg::keygen_verb(command),
        // Built without the tunnel, so there is no keypair to mint and no
        // implementation to mint it with. An HONEST REFUSAL naming the missing
        // feature, never the generic "only server commands" below: an operator
        // typing `gunnar wg` at a binary that was deliberately built without
        // WireGuard must be told THAT, not left to wonder about a typo.
        #[cfg(not(feature = "wg-client"))]
        Command::Wg(_) => anyhow::bail!(
            "this binary was built WITHOUT the `wg-client` feature: it contains no \
             WireGuard implementation, so `gunnar wg` has no keypair to mint. Rebuild \
             with --features wg-client, or use a build that has it."
        ),

        #[allow(unreachable_patterns)]
        _ => anyhow::bail!("gunnar-server handles only server commands"),
    }
}

/// Replication to a second gunnar. **On the appliance since
/// 2026-08-30.**
///
/// The push half is `gunnar-client`'s send-pack, proved against the real
/// `git receive-pack` binary, and writing a second one here to keep a dependency
/// edge clean would trade one implementation for a graph assertion. The gate used to be the
/// `client` feature, which is why this doc comment used to end *"the appliance
/// build does not replicate yet … closing the gap is a manifest change"*.
///
/// It was that manifest change, and it has landed. `gunnar-client` and
/// `nornir-git` each put their gix porcelain behind a default-on `porcelain`
/// feature, so the WIRE half — send-pack, the russh dialler, the ref plan —
/// resolves alone, and the gate is now `replication`. MEASURED 2026-08-30,
/// `cargo tree -p gunnar-server -e normal`: **365** packages without it,
/// **369** with it, against **493** under the old all-or-nothing `client`.
/// `.nornir/build-variants.toml`'s `gunnar-server` row names the feature, so the
/// packed appliance carries it; the two-appliance drill exercises it.
#[cfg(feature = "replication")]
pub mod mirror;
/// **I11's passive heartbeat**: the standby half of failover, which notices a
/// primary that died without abdicating and takes over by itself. Off unless a
/// `--watch-primary` says otherwise — `control_plane.rs` states the rule it
/// obeys, that naming a new primary is a decision and never a default.
#[cfg(feature = "replication")]
pub mod standby_watch;
/// **The estate repairs itself** (5.4): the boot check (auto-fix OFF by default)
/// and the pull channel to monetize, both through `gunnar-repair` — see the module.
#[cfg(feature = "replication")]
pub mod repair;

/// S10: the main mails a sold estate's resend sign-in link itself — see the module. Only a
/// build that pulls the order (`fleet-pull`) mails it.
#[cfg(feature = "fleet-pull")]
pub mod postman;

/// G16-d: a sold estate's paid data step, pulled (`grow_go_ahead`) — the box's half.
#[cfg(feature = "replication")]
pub mod paid_growth;
/// `Report.product` (ONE monetize, G1 pull): the main's own word about its product.
#[cfg(feature = "replication")]
pub mod product_report;
pub mod replication_view;

/// **Outbound webhooks: delivery to an external URL.** A subscriber to the
/// same bus the replicator consumes, gated behind the `webhooks` feature — off,
/// `ControlPlane`'s default bodies refuse the verbs by name.
#[cfg(feature = "webhooks")]
pub mod webhook;

/// **Replication's configuration, over the control plane.** The seam between
/// [`mirror`] (which knows nothing about gRPC) and [`control_plane`] (which
/// knows nothing about replication's types).
///
/// Behind `replication` for the same reason [`mirror`] is: with the feature off
/// there is nothing to configure, and `ControlPlane`'s default bodies refuse the
/// three verbs by name.
#[cfg(feature = "replication")]
pub mod replication_wire;

/// **Record one functional-status row for the nornir test matrix.**
///
/// Wraps `nornir_testmatrix::functional_status` behind the `testmatrix`
/// feature — a compiled-out `#[inline]` no-op otherwise, with no nornir edge in
/// a release appliance. The appliance rollup
/// (`edda/crates/nornir-testmatrix/src/appliance.rs`) folds rows whose
/// `component` is `"<product>-appliance"` (host form) or
/// `"<product>-appliance/<form>"`, so the ONE string gunnar's host form spells
/// is `"gunnar-appliance"`. Same shim as holger `server/cli/src/main.rs:1095`.
#[inline]
pub fn functional_status(component: &str, check: &str, ok: bool, detail: &str) {
    #[cfg(feature = "testmatrix")]
    nornir_testmatrix::functional_status(component, check, ok, detail);
    #[cfg(not(feature = "testmatrix"))]
    {
        let _ = (component, check, ok, detail);
    }
}

pub use attest::{AttestationLog, ATTESTATION_LOG};
pub use control_plane::CoreControlPlane;
pub use grpc_gate::{activity_ledger, control_audit_ledger, control_transfer_gate};
#[cfg(feature = "replication")]
pub use mirror::{
    ReplicationConfig, ReplicationCounters, ReplicationScope, ReplicationTarget, Replicator,
    TargetStatus, VisibilityScope,
};
pub use serve::{GrpcTls, ServeConfig, Server};
pub use stores::{ServerStores, CONTROL_DB, CONTROL_DIR};
pub use transport::{WireConfig, WireTransport};

/// **`--admit-limit` / `--admit-window-days` → the usage share's settings.**
/// Absent flags take `edda_admit::Settings::default()`; `none` is PassThrough.
/// A typo is a startup error naming the flag.
#[cfg(feature = "admit-share")]
pub fn admit_settings(
    limit: Option<&str>,
    window_days: Option<u32>,
) -> anyhow::Result<gunnar_ssh::edda_admit::Settings> {
    let mut settings = gunnar_ssh::edda_admit::Settings::default();
    if let Some(limit) = limit {
        settings.limit = gunnar_ssh::edda_admit::parse_bytes(limit)
            .map_err(|err| anyhow::anyhow!("--admit-limit (GUNNAR_ADMIT_LIMIT): {err}"))?;
    }
    if let Some(days) = window_days {
        anyhow::ensure!(days > 0, "--admit-window-days (GUNNAR_ADMIT_WINDOW_DAYS) must be at least 1");
        settings.window_days = days;
    }
    Ok(settings)
}