gunnar-grpc 1.5.0

The tonic service implementations and the event bus behind gunnar's gRPC control plane.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
//! The seven service implementations (the gRPC control plane).
//!
//! Each is a thin shell: validate the request, call the storage seam, map the
//! error, and, where the operation changes the world, publish an event
//! (the event stream). No service holds state of its own; all of it is behind
//! [`ControlPlane`](crate::seam::ControlPlane) or on the bus.
//!
//! **Every one of them authorises before it acts.** The first statement of
//! every RPC body is a [`ServiceContext::authorize`] (or
//! [`ServiceContext::caller`] followed by [`ServiceContext::require`], where
//! the scope is inside the message). There is no RPC in this crate that
//! reaches the storage seam without one, and
//! `tests/auth.rs::every_rpc_refuses_an_unauthenticated_caller` drives all 58
//! of them **with no interceptor in the way** so a handler that forgot goes
//! red rather than serving.
//!
//! `clippy::result_large_err` is allowed for the same reason it is allowed in
//! [`crate::status`]: `tonic::Status` is a large error type by design and every
//! RPC in the surface returns one. Boxing it would fight the framework for no
//! gain.
#![allow(clippy::result_large_err)]

use std::sync::Arc;
use std::time::Instant;

use gunnar_proto::GrantLevel;
use tonic::{Request, Status};

use crate::auth::{audit_allowed, audit_denied, Access, AuthMode, Caller};
use crate::bus::EventBus;
use crate::config::ServerConfig;
use crate::seam::ControlPlane;

pub mod access;
pub mod accounts;
pub mod admin;
#[cfg(feature = "metered")]
pub mod billing;
pub mod bootstrap;
/// Clans (2026-10-05): the person's own verbs, decided from the clan.
pub mod clans;
#[cfg(feature = "metered")]
pub mod entitlement;
/// The named sealed slot (2026-10-09): bytes under a name, its owner's alone.
#[cfg(feature = "metered")]
pub mod sealed;
/// The owner's enterprise list: the account audit log, the operator's aggregates and look, the name filter.
pub mod enterprise;
pub mod events;
pub mod forge;
/// Always compiled, and on purpose: the community appliance reports the same
/// seven meters and has the same three doors, so the table describing them is
/// not a metered thing. Only [`meters::caps_gunnar_cannot_honour`]'s CALLER is
/// (`Entitlement.Set` does not exist without `metered`).
pub mod meters;
pub mod mirror;
pub mod policy;
pub mod pulls;
pub mod refs;
pub mod repos;
pub mod sso;
pub mod usage;
pub mod usage_batch;
pub mod webhook;

/// **How much of a collection a caller may be shown.**
///
/// The answer [`ServiceContext::require_or_narrow`] gives where
/// [`ServiceContext::require`] would have given a `PERMISSION_DENIED`, and the
/// input [`ServiceContext::retain_readable`] filters on.
///
/// It is an enum with two variants rather than a `bool` because the two are not
/// degrees of the same thing: `Everything` means *the grant already covers the
/// scope, ask nothing further*, and `PublicSubset` means *ask about every row
/// individually*. A `bool` named `filter` would read as an optimisation that
/// could be skipped, and skipping it is a disclosure.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Listing {
    /// The caller holds `Read` over the scope that was asked about. Every row
    /// is served, private ones included. **This is the pre-existing behaviour
    /// and the only one an operator ever sees.**
    Everything,
    /// The caller holds no grant over the scope. Rather than being refused,
    /// each row is put to `AuthTable::may` on its own; what survives is what
    /// a public repository is readable without a grant, plus anything the caller owns or was
    /// granted at repository scope.
    PublicSubset,
}

/// What every service needs: the storage seam, the event bus, and the config
/// (so `Admin.Health` can report which addresses are actually bound).
#[derive(Clone)]
pub struct ServiceContext {
    pub control: Arc<dyn ControlPlane>,
    pub bus: Arc<EventBus>,
    pub config: ServerConfig,
    pub started: Instant,
    /// **The admission gate the transfer-shaped RPCs take a permit from**
    /// (one implementation), reached through the [`crate::TransferGate`] seam.
    ///
    /// `Some` when `gunnar-server` handed in the SSH listener's own
    /// `Arc<Admission>` (via [`ServiceContext::with_transfer_gate`]), so a
    /// `Repos.ArchiveOut` counts against the same server-wide limit, the same
    /// per-principal share and the same memory budget as a `git-upload-pack`
    /// over SSH or HTTP. `None` on a plaintext test router or a listener built
    /// without a gate: the transfer-shaped RPCs then run **ungated**, which is
    /// the exact ungated state the red half of the test drives — a green
    /// there proves nothing, so the gate has to be wired for the refusal to
    /// exist at all.
    pub transfer_gate: Option<Arc<dyn crate::TransferGate>>,
    /// **Is this box a browse replica**? The server's
    /// `ServerStores::browse()` flag, shared by `Arc`, flipped by the
    /// replication slot. `None` (a bare context, a test) is never a browse
    /// box. While `true`, [`ServiceContext::require`] refuses every mutator
    /// by name — see [`BROWSE_STILL_ANSWERS`].
    pub browse: Option<Arc<std::sync::atomic::AtomicBool>>,
    /// **The durable audit trail** (GUN-044), or `None` for the `tracing`
    /// record alone.
    ///
    /// `Some` when `gunnar-server` handed in the hash-chained ledger it keeps
    /// beside the rest of the control state (via
    /// [`ServiceContext::with_audit_ledger`]), so every decision the audit
    /// functions record is also written somewhere a removed line shows up as a
    /// broken chain. `None` on a test router: the `tracing` record is unchanged
    /// either way, which is what makes the ledger additive rather than a second
    /// trail that can disagree with the first.
    pub audit: Option<Arc<dyn crate::auth::AuditLedger>>,
    /// **The method-blind credentials this listener has been shown**
    /// ([`crate::auth::UnboundCredentialLog`]).
    ///
    /// Minted here rather than inside [`crate::auth::AuthGate`] because the
    /// gate writes it and `Admin.Health` reads it, and those are two different
    /// objects on one listener: `router_with_trust` hands this same `Arc` to
    /// the gate it builds. A context whose gate was built elsewhere reports an
    /// empty log, which is the honest answer — it has seen nothing — and never
    /// a wrong one.
    pub unbound: Arc<crate::auth::UnboundCredentialLog>,
    /// **The dual heartbeat's primary half** ([`crate::heartbeat::Registry`]):
    /// every `Mirror.LogHead` carrying a `TwinReport` is recorded here and
    /// `Admin.Health` reads it back. One per listener, like `unbound`.
    pub heartbeats: Arc<crate::heartbeat::Registry>,
    /// **The machine principals that call this listener**, newest call each
    /// ([`crate::topology::CallerLog`]): written by [`ServiceContext::caller`],
    /// read by `Admin.Topology` — the consoles and the front's other keys are
    /// boxes of the estate, and their calls are the evidence that they are up.
    pub callers: Arc<crate::topology::CallerLog>,
    /// **The spent `(fingerprint, nonce)` pairs of this listener**, one guard
    /// for both doors that check a credential: `router_with_trust` hands this
    /// `Arc` to the gate, and `Bootstrap.Restore` checks its own credential
    /// against it ([`crate::auth::authenticate_restore`]) — so a header spent
    /// at one door is spent at the other.
    pub replay: Arc<crate::auth::ReplayGuard>,
    /// **The web challenges this listener has issued** (`Access.WebChallenge`,
    /// Stage L) and not yet seen answered: single use, five minutes.
    pub web_challenges: Arc<crate::webauthn::Challenges>,
    /// **The machine enrolment requests waiting for an admin**
    /// (`Access.RequestMachineEnrolment`, 2026-10-05): fifteen minutes each,
    /// in this listener's memory, like the challenges.
    pub machine_enrolments: Arc<crate::machine_enrolment::Requests>,
    /// **Company SSO's memory on this listener** ([`crate::sso::SsoState`]):
    /// the sign-ins begun, the second-factor tickets, the SAML assertions
    /// consumed, the IdPs' keys, the rate windows — and the egress
    /// `gunnar-server` hands in.
    pub sso: Arc<crate::sso::SsoState>,
    /// **This estate SELLS room** (the feed's `sells_room`, LIVE; [`crate::selling`]): a
    /// person's own account with no fact holds no room here until its fact lands. The same
    /// `Arc` the entitlement slot's push gate reads.
    pub sells_room: Arc<std::sync::atomic::AtomicBool>,
    /// **The runtime the handlers run on** (R5.1-G), or `None` to run them on
    /// the runtime that serves the connections.
    ///
    /// The control-plane handlers are `async fn`s that make SYNCHRONOUS calls
    /// into the store: a `StorageInfo` disk walk, a txlog fsync, a snapshot.
    /// Each one holds the worker it runs on until it returns. When they ran on
    /// the runtime that also drives the HTTP/2 connections, enough of them at
    /// once held every worker, nothing polled the connection, a client's
    /// keepalive PING went unanswered, and the client killed the connection —
    /// failing every call in flight on it, the slow ones and the quick ones
    /// alike (`KeepAliveTimedOut` on the console, an `Internal` stream reset
    /// on monetize's `Entitlement.Set`). `gunnar-server` hands in a runtime of
    /// its own here ([`ServiceContext::with_handler_runtime`]), so the
    /// connections are driven by workers no handler can hold.
    pub handlers: Option<tokio::runtime::Handle>,
}

/// **What a browse replica's control plane still answers at Write/Admin
/// level.** Every other method that needs more than `Read` is refused with
/// `FAILED_PRECONDITION` while the box is a browse replica: it holds only
/// public repositories, serves strangers, and must never be promoted or
/// reshaped by anyone — a stolen admin key on the public-facing box buys
/// nothing but a read. The list is the Admin-level READS (listings the
/// operator uses to check the box), plus the two writes the design needs:
///
/// * `Mirror.SetRole` — so the box can be narrowed to `STANDBY`; the handler
///   refuses `PRIMARY` on a browse box itself.
/// * `Repos.Delete` — **from a `Machine` only**: the primary's PublicOnly
///   replication deletes a repository here when it goes Private. A person's
///   admin key cannot delete on a browse box; the primary's deploy key can.
/// * `Repos.Create` — **from a `Machine` only**, the delete's other half
///   (2026-09-22): a repository the browse box forgot comes back by a push,
///   and a push-create is sha1 by construction (`push_create_spec`), so a
///   sha256 repository could never return on its own. The primary's
///   replicator pre-creates it here, in the repository's own object format,
///   and then pushes. Same key, same standing as the delete.
pub const BROWSE_STILL_ANSWERS: &[&str] = &[
    "Access.ListGrants",
    "Access.ListGroupMembers",
    "Access.ListGroupMembershipEvents",
    "Access.ListKeys",
    "Access.ListPrincipals",
    "Mirror.ReplicationStatus",
    // The twin (2026-09-15): a twin is frozen like a browse replica, and its log is what a
    // restore reads. A read of the log mutates nothing; the level is Admin because the
    // log carries every sealed secret, not because it writes.
    "Mirror.LogFetch",
    "Mirror.SnapshotFetch",
    "Webhooks.ListWebhooks",
    "Mirror.SetRole",
    "Repos.Delete",
    "Repos.Create",
];

/// **What a TWIN answers beyond [`BROWSE_STILL_ANSWERS`]**: its OWN data-set growth (owner
/// ruling 2026-09-22, T21 — the twin, built from the same medium, grows EXACTLY like main and
/// is never restarted for it). A twin is frozen like a browse replica, so without this the grow
/// verbs were refused there and the only way left to grow it was stop → resize → start. Passed
/// only on a twin ([`ControlPlane::is_twin`]), never on a public browse box; the go-ahead is
/// still checked by the grow machine as on main.
pub const TWIN_STILL_ANSWERS: &[&str] = &["Admin.GrowStart", "Admin.GrowResume", "Admin.GrowStatus"];

/// **What a principal is to the account it acts in** — see
/// [`ServiceContext::standing`]. `role` is `admin`, `individual`, `employee`,
/// or empty with no account.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Standing {
    pub account: String,
    pub role: &'static str,
    /// The account admin's display name: whom a member asks for more room.
    pub admin: String,
}

impl ServiceContext {
    pub fn new(control: Arc<dyn ControlPlane>, bus: Arc<EventBus>, config: ServerConfig) -> Self {
        Self {
            control,
            bus,
            config,
            started: Instant::now(),
            transfer_gate: None,
            browse: None,
            audit: None,
            unbound: Arc::new(crate::auth::UnboundCredentialLog::default()),
            heartbeats: Arc::new(crate::heartbeat::Registry::default()),
            callers: Arc::new(crate::topology::CallerLog::default()),
            replay: Arc::new(crate::auth::ReplayGuard::default()),
            web_challenges: Arc::new(crate::webauthn::Challenges::default()),
            machine_enrolments: Arc::new(crate::machine_enrolment::Requests::default()),
            sso: Arc::new(crate::sso::SsoState::default()),
            sells_room: Arc::new(std::sync::atomic::AtomicBool::new(false)),
            handlers: None,
        }
    }

    /// The process's one "this estate sells room" flag ([`ServiceContext::sells_room`]).
    pub fn with_sells_room(mut self, flag: Arc<std::sync::atomic::AtomicBool>) -> Self {
        self.sells_room = flag;
        self
    }

    /// Run every control-plane handler on `handlers` rather than on the
    /// runtime that drives the HTTP/2 connections — see
    /// [`ServiceContext::handlers`].
    pub fn with_handler_runtime(mut self, handlers: tokio::runtime::Handle) -> Self {
        self.handlers = Some(handlers);
        self
    }

    /// Write every decision to `ledger` as well as to `tracing`.
    pub fn with_audit_ledger(mut self, ledger: Arc<dyn crate::auth::AuditLedger>) -> Self {
        self.audit = Some(ledger);
        self
    }

    /// Use `registry` for the heartbeat (a test shortens the bounds).
    pub fn with_heartbeats(mut self, registry: Arc<crate::heartbeat::Registry>) -> Self {
        self.heartbeats = registry;
        self
    }

    /// The ledger, as the audit functions take it.
    fn ledger(&self) -> Option<&dyn crate::auth::AuditLedger> {
        self.audit.as_deref()
    }

    /// Share the browse flag in (`ServerStores::browse()`).
    pub fn with_browse_flag(mut self, browse: Arc<std::sync::atomic::AtomicBool>) -> Self {
        self.browse = Some(browse);
        self
    }

    /// Is this box a browse replica right now?
    pub fn is_browse(&self) -> bool {
        self.browse
            .as_ref()
            .is_some_and(|flag| flag.load(std::sync::atomic::Ordering::SeqCst))
    }

    /// **The browse replica's mutator refusal**,
    /// asked before the grant: a `Read` always passes; anything higher passes
    /// only if [`BROWSE_STILL_ANSWERS`] names the method — and `Repos.Delete`
    /// and `Repos.Create` only from a `Machine`. Before the grant so the refusal is the same for
    /// everyone, admin or not: the box's answer, not the caller's standing.
    fn refuse_on_browse(
        &self,
        caller: &Caller,
        level: GrantLevel,
        method: &'static str,
    ) -> Result<(), Status> {
        if level == GrantLevel::Read || !self.is_browse() {
            return Ok(());
        }
        let allowed = match method {
            "Repos.Delete" | "Repos.Create" => caller.machine,
            other => BROWSE_STILL_ANSWERS.contains(&other) || (TWIN_STILL_ANSWERS.contains(&other) && self.control.is_twin()),
        };
        if allowed {
            return Ok(());
        }
        tracing::info!(
            target: crate::auth::AUDIT_TARGET,
            event = "control.browse_refused",
            method,
            principal = %caller.principal_id,
            machine = caller.machine,
            "a browse replica refused a control-plane mutator"
        );
        Err(Status::failed_precondition(format!(
            "this box is a browse replica: it holds public repositories read-only and cannot be \
             promoted or changed here — {method} is refused; run it against the primary"
        )))
    }

    /// Route this context's transfer-shaped RPCs (`Repos.ArchiveOut`) through an
    /// **existing** admission gate — the SSH listener's own (one implementation).
    ///
    /// `gunnar-server` passes the same `Arc<Admission>` the SSH and HTTP fronts
    /// hold, wrapped in its [`crate::TransferGate`] adapter, so all three front
    /// doors meter against one ceiling. Without this call the control plane's
    /// archive stream is the one transfer on the box with no backpressure.
    pub fn with_transfer_gate(mut self, gate: Arc<dyn crate::TransferGate>) -> Self {
        self.transfer_gate = Some(gate);
        self
    }

    /// The verified caller [`crate::auth::AuthGate`] attached to this request.
    ///
    /// **This is where deny-by-default lives.** No extension means no verified
    /// credential, whether because the caller sent none or because someone
    /// mounted these services without the gate in front of them. The answer to
    /// that is `UNAUTHENTICATED`, never "assume an admin".
    pub fn caller<T>(&self, request: &Request<T>) -> Result<Caller, Status> {
        if let Some(caller) = request.extensions().get::<Caller>() {
            // A MACHINE's call is evidence that its box is up (`Admin.Topology`);
            // a person's laptop is not a box of the estate, so people are not
            // recorded.
            if caller.machine && caller.authenticated {
                let address = request.remote_addr().map(|a| a.to_string()).unwrap_or_default();
                self.callers.note(&caller.principal_id, &address, estate_clock::now_unix_ms(), false);
            }
            return Ok(caller.clone());
        }
        if self.config.auth == AuthMode::AllowAllInsecure {
            return Ok(Caller {
                principal_id: Caller::INSECURE_PRINCIPAL.to_owned(),
                fingerprint: String::new(),
                machine: false,
                authenticated: false,
            });
        }
        Err(crate::auth::AuthFailure::Missing.status())
    }

    /// **Is this call about the caller's OWN principal?** (2026-10-04, the
    /// self-scope.) True only for a caller the gate authenticated by one of
    /// `target`'s own keys — never for the insecure escape hatch's stand-in,
    /// never for an empty target.
    pub fn is_self(&self, caller: &Caller, target: &str) -> bool {
        caller.authenticated
            && !caller.principal_id.is_empty()
            && caller.principal_id == target
            && caller.principal_id != Caller::INSECURE_PRINCIPAL
    }

    /// **The self-scope check, and its audit record** (owner, 2026-10-04: the
    /// console calls gunnar AS THE PERSON). A principal may manage its OWN keys
    /// — add one, list them, revoke one — without any grant: the call is
    /// already that principal's own signed word, which is exactly the consent
    /// `key_attestation::Requirement::CallerIsTarget` names. What still
    /// applies is everything that is not a grant: a browse replica refuses the
    /// mutators it refuses for everybody, and a NARROWED principal acts only
    /// through the methods on its list.
    ///
    /// No account gate is consulted, on purpose: a gate shuts the ACCOUNT's
    /// data, and a key is not data — a locked-out person who adds a key reaches
    /// nothing through it that the gate does not still refuse.
    pub fn require_self(
        &self,
        caller: &Caller,
        level: GrantLevel,
        method: &'static str,
    ) -> Result<(), Status> {
        self.refuse_on_browse(caller, level, method)?;
        if self.config.auth != AuthMode::AllowAllInsecure {
            match self.control.narrowed_methods(&caller.principal_id) {
                Ok(Some(allowed)) if !allowed.iter().any(|m| m == method) => {
                    audit_denied(
                        method,
                        caller,
                        &Access::Server,
                        level,
                        self.config.auth,
                        "outside its narrowed grant",
                        self.ledger(),
                    );
                    return Err(Status::permission_denied(format!(
                        "principal {} is narrowed to [{}]: {method} is outside its narrowed grant",
                        caller.principal_id,
                        allowed.join(", ")
                    )));
                }
                Ok(_) => {}
                Err(err) => {
                    return Err(Status::unavailable(format!(
                        "the identity table could not be read: {err}"
                    )))
                }
            }
        }
        crate::auth::audit_self(method, caller, self.config.auth, self.ledger());
        Ok(())
    }

    /// **The decision [`ServiceContext::require`] makes, asked without acting**
    /// (`Access.Check`, 2026-10-04). Same order — a browse replica, a NARROWED
    /// principal's list, the insecure escape hatch, then
    /// [`crate::seam::Identity::may`] (grants, groups, account ownership, the
    /// account gate) — and the same sentence for a refusal. `Ok(None)` is
    /// allowed, `Ok(Some(why))` is not; a storage fault is an error, never a
    /// `no`.
    ///
    /// **Nothing is written to the audit trail.** A "may I?" asked before a
    /// button is drawn is not a call that was refused, and a `decision="deny"`
    /// WARN line per page view would drown the refusals an operator greps for.
    /// The verb, when it is called, records itself as it always did.
    ///
    /// A browse replica answers nothing above `Read` but its own listings, and
    /// a check does not know which verb is coming, so above `Read` it says no
    /// there; a narrowed principal's grants are never consulted, so it says no
    /// to every scope.
    pub fn check(
        &self,
        caller: &Caller,
        access: Access,
        level: GrantLevel,
    ) -> Result<Option<String>, Status> {
        if level != GrantLevel::Read && self.is_browse() {
            return Ok(Some(
                "this box is a browse replica: it holds public repositories read-only, and \
                 nothing above Read is done here; run it against the primary"
                    .to_owned(),
            ));
        }
        if self.config.auth == AuthMode::AllowAllInsecure {
            return Ok(None);
        }
        match self.control.narrowed_methods(&caller.principal_id) {
            Ok(Some(allowed)) => {
                return Ok(Some(format!(
                    "principal {} is narrowed to [{}]: its grants are not consulted",
                    caller.principal_id,
                    allowed.join(", ")
                )))
            }
            Ok(None) => {}
            Err(err) => {
                return Err(Status::unavailable(format!(
                    "the identity table could not be read: {err}"
                )))
            }
        }
        match self.control.may(&caller.principal_id, &access, level) {
            Ok(true) => Ok(None),
            Ok(false) => Ok(Some(format!(
                "principal {} has no {} grant on {access}",
                caller.principal_id,
                crate::auth::level_name(level)
            ))),
            Err(err) => Err(Status::unavailable(format!(
                "the identity table could not be read: {err}"
            ))),
        }
    }

    /// **The grant check, and the audit record.** One call site shape for all
    /// 37 RPCs (one implementation), so the decision and the trail cannot drift apart.
    ///
    /// The decision itself is [`crate::seam::Identity::may`], which is
    /// `AuthTable::may` in the real server. It does not compute it; it asks,
    /// records the answer, and turns a `false` into `PERMISSION_DENIED`.
    pub fn require(
        &self,
        caller: &Caller,
        access: Access,
        level: GrantLevel,
        method: &'static str,
    ) -> Result<(), Status> {
        self.refuse_on_browse(caller, level, method)?;
        // A NARROWED principal (f-principal-reach): its list, and nothing else
        // — whatever grants it might hold are not consulted.
        if self.config.auth != AuthMode::AllowAllInsecure {
            match self.control.narrowed_methods(&caller.principal_id) {
                Ok(Some(allowed)) => {
                    if allowed.iter().any(|m| m == method) {
                        audit_allowed(method, caller, &access, level, self.config.auth, self.ledger());
                        return Ok(());
                    }
                    audit_denied(method, caller, &access, level, self.config.auth, "outside its narrowed grant", self.ledger());
                    return Err(Status::permission_denied(format!(
                        "principal {} is narrowed to [{}]: {method} is outside its narrowed grant",
                        caller.principal_id,
                        allowed.join(", ")
                    )));
                }
                Ok(None) => {}
                Err(err) => {
                    audit_denied(method, caller, &access, level, self.config.auth, "grant lookup failed", self.ledger());
                    return Err(Status::unavailable(format!("the identity table could not be read: {err}")));
                }
            }
        }
        if self.config.auth == AuthMode::AllowAllInsecure {
            audit_allowed(
                method,
                caller,
                &access,
                level,
                self.config.auth,
                self.ledger(),
            );
            return Ok(());
        }
        match self.control.may(&caller.principal_id, &access, level) {
            Ok(true) => {
                audit_allowed(
                    method,
                    caller,
                    &access,
                    level,
                    self.config.auth,
                    self.ledger(),
                );
                Ok(())
            }
            Ok(false) => {
                audit_denied(
                    method,
                    caller,
                    &access,
                    level,
                    self.config.auth,
                    "no grant",
                    self.ledger(),
                );
                Err(Status::permission_denied(format!(
                    "principal {} has no {} grant on {access}",
                    caller.principal_id,
                    crate::auth::level_name(level)
                )))
            }
            // A storage fault is not a denial. Reporting it as one would turn
            // an outage into a silent, confusing permission problem (the ACL model: `may`
            // returns `Result<bool>` for exactly this reason).
            Err(err) => {
                audit_denied(
                    method,
                    caller,
                    &access,
                    level,
                    self.config.auth,
                    "grant lookup failed",
                    self.ledger(),
                );
                Err(Status::unavailable(format!(
                    "the identity table could not be read: {err}"
                )))
            }
        }
    }

    /// **The narrowest scope that CAN hold a grant**: the repository, and the
    /// namespace when the repository cannot hold one.
    ///
    /// MEASURED on the appliance 2026-08-30 (`v1.1.3-227-g6a2367a`, requests
    /// `175daccc0000000b` and `…0e`):
    ///
    /// ```text
    /// repo delete alice/linux --purge --yes
    ///   -> principal alice has no admin grant on repo:alice/linux
    /// ```
    ///
    /// …on a store that was on the disk, holding tens of gigabytes, with no
    /// catalog row. [`crate::seam::Identity::may`] answers `false` for a grant
    /// on a repository that is not in the catalog — deliberately, so nobody
    /// can probe the ACL of repositories they cannot see — so a repo-scoped
    /// check on a repository the catalog has lost refuses **everyone**,
    /// gunnar-admin included. The verb that reclaims a lost repository was
    /// gated on the record whose absence is the whole problem.
    ///
    /// The soft-delete work hit exactly this on `Repos.Undelete` and `Repos.Purge` and
    /// answered it by scoping them to the namespace. This is that same answer,
    /// written once for every verb that has to work on a repository the
    /// catalog may not hold (one implementation) rather than a second rule beside it.
    ///
    /// **It is not a widening.** `AuthTable::may` already takes the maximum
    /// over every grant whose scope `covers` the repository, and
    /// `Scope::Namespace(n).covers(repo)` is `n == repo.namespace`; account
    /// ownership is the same one rule on both sides. So for a repository that
    /// IS in the catalog, `may_namespace(ns) => may(repo)` and the second
    /// question can only ever be answered `true` where the first already was.
    /// What it adds is the case where the first question has no repository to
    /// ask about — and there it is the *narrower* answer, because a
    /// repo-scoped grant cannot be evaluated against a row that is gone.
    ///
    /// The refusal names **the repository**, never the namespace, so which of
    /// the two questions was asked is not an existence oracle for a caller
    /// with no grant at all.
    pub fn require_repo_or_namespace(
        &self,
        caller: &Caller,
        path: &gunnar_proto::RepoPath,
        level: GrantLevel,
        method: &'static str,
    ) -> Result<(), Status> {
        let asked = Access::repo(path);
        self.refuse_on_browse(caller, level, method)?;
        if self.config.auth == AuthMode::AllowAllInsecure {
            audit_allowed(
                method,
                caller,
                &asked,
                level,
                self.config.auth,
                self.ledger(),
            );
            return Ok(());
        }
        let unavailable = |err: crate::seam::ControlError| {
            audit_denied(
                method,
                caller,
                &asked,
                level,
                self.config.auth,
                "grant lookup failed",
                self.ledger(),
            );
            Status::unavailable(format!("the identity table could not be read: {err}"))
        };
        match self.control.may(&caller.principal_id, &asked, level) {
            Ok(true) => {
                audit_allowed(
                    method,
                    caller,
                    &asked,
                    level,
                    self.config.auth,
                    self.ledger(),
                );
                return Ok(());
            }
            Ok(false) => {}
            Err(err) => return Err(unavailable(err)),
        }
        let wider = Access::namespace(&path.namespace);
        match self.control.may(&caller.principal_id, &wider, level) {
            Ok(true) => {
                audit_allowed(
                    method,
                    caller,
                    &wider,
                    level,
                    self.config.auth,
                    self.ledger(),
                );
                Ok(())
            }
            Ok(false) => {
                audit_denied(
                    method,
                    caller,
                    &asked,
                    level,
                    self.config.auth,
                    "no grant",
                    self.ledger(),
                );
                Err(Status::permission_denied(format!(
                    "principal {} has no {} grant on {asked}",
                    caller.principal_id,
                    crate::auth::level_name(level)
                )))
            }
            Err(err) => Err(unavailable(err)),
        }
    }

    /// **The grant question a LISTING asks**: not "may you?" but "how much of
    /// it may you see?"
    ///
    /// [`ServiceContext::require`] has exactly two answers, and for a listing
    /// the wrong one is the refusal. A caller with no grant on a namespace is
    /// not a caller who may not *ask* what is in it — since anonymous read it is a caller
    /// who may see the [`Visibility::Public`](gunnar_core) rows and no others.
    /// Refusing outright is what made `Repo.visibility` undiscoverable: a
    /// stranger could clone `nordisk/gunnar` if somebody told them the path,
    /// and had no way to learn the path existed.
    ///
    /// So this returns a [`Listing`] rather than `()`, and the caller feeds it
    /// to [`ServiceContext::retain_readable`].
    ///
    /// # Why the scope check still runs first
    ///
    /// [`Listing::Everything`] is the *fast* answer and the *unchanged* one: a
    /// caller holding `Read` over the whole scope gets the whole collection in
    /// one grant lookup, exactly as before this existed (one implementation). The narrowing
    /// is reached only where a refusal would have been, so no caller that could
    /// see a row yesterday stops seeing it today, and no caller pays the
    /// per-row cost unless it is the only thing standing between them and a
    /// `PERMISSION_DENIED`.
    ///
    /// # Why a storage fault is still a refusal
    ///
    /// An `Err` from the identity table is `UNAVAILABLE`, never a narrowing.
    /// Degrading an unreadable ACL into "show them the public subset" would
    /// make an outage look like a permissions change, and — worse — would make
    /// the *safest-looking* branch the one that runs when the check is broken.
    pub fn require_or_narrow(
        &self,
        caller: &Caller,
        access: Access,
        method: &'static str,
    ) -> Result<Listing, Status> {
        let level = GrantLevel::Read;
        self.refuse_on_browse(caller, level, method)?;
        if self.config.auth == AuthMode::AllowAllInsecure {
            audit_allowed(
                method,
                caller,
                &access,
                level,
                self.config.auth,
                self.ledger(),
            );
            return Ok(Listing::Everything);
        }
        match self.control.may(&caller.principal_id, &access, level) {
            Ok(true) => {
                audit_allowed(
                    method,
                    caller,
                    &access,
                    level,
                    self.config.auth,
                    self.ledger(),
                );
                Ok(Listing::Everything)
            }
            // NOT `audit_denied`: nothing was denied. A record saying `deny`
            // here would put a warning in the log for every visitor reading the
            // front page, and would hide the real denials an operator greps
            // for. Its own decision word instead, so "who browsed anonymously"
            // and "who was refused" stay different questions.
            Ok(false) => {
                crate::auth::audit_narrowed(
                    method,
                    caller,
                    &access,
                    level,
                    self.config.auth,
                    self.ledger(),
                );
                Ok(Listing::PublicSubset)
            }
            Err(err) => {
                audit_denied(
                    method,
                    caller,
                    &access,
                    level,
                    self.config.auth,
                    "grant lookup failed",
                    self.ledger(),
                );
                Err(Status::unavailable(format!(
                    "the identity table could not be read: {err}"
                )))
            }
        }
    }

    /// Drop the rows `caller` may not read, one repository at a time.
    ///
    /// # Why this asks `may` again instead of reading `visibility`
    ///
    /// Because `AuthTable::may` is **the** ACL (one implementation), and "public" is only one
    /// of the four things it decides. A filter here that kept the rows whose
    /// `Repo.visibility` field said `PUBLIC` would be a second grant decision
    /// living beside the first, free to disagree with it — and the one-sandbox rule records
    /// which one wins when two disagree. Concretely, that filter would be wrong
    /// in three directions at once:
    ///
    /// * it would **hide a caller's own account** from them. A principal is
    ///   unconditionally admin of the namespace named after it, with no grant
    ///   anywhere, so `vetra` listing repositories must see `vetra/secrets`.
    /// * it would **ignore the account gate**. `may` reads
    ///   [`crate::seam::ControlPlane`]'s gate *above* every other rule, in both
    ///   directions, precisely so a stranger cannot pull bytes out of an
    ///   account a purge is walking. A visibility test would put that account's
    ///   public repositories straight back on the list.
    /// * it would **miss a repo-scoped grant**, so a caller granted `Read` on
    ///   one private repository would not find it in a listing that named it.
    ///
    /// All three are arms of `tests/public_listing.rs` in `gunnar-cli`, driven
    /// against the real `AuthTable` rather than a double, because a double that
    /// answered `may` out of a grant map would be green on every one of them.
    ///
    /// # The cost, and who pays it
    ///
    /// One `may` per row, and `AuthTable::may` reads the identity document each
    /// time. Only [`Listing::PublicSubset`] pays it — a caller with a grant
    /// returns on the first line — so the per-row cost falls exactly on the
    /// grantless caller reading a front page, and never on an operator listing
    /// a large server.
    pub fn retain_readable(
        &self,
        caller: &Caller,
        repos: Vec<gunnar_proto::Repo>,
        listing: Listing,
    ) -> Result<Vec<gunnar_proto::Repo>, Status> {
        let mut readable = Vec::new();
        let mut closed_ns: std::collections::HashMap<String, bool> = std::collections::HashMap::new();
        // ★ The rows that need the ACL, asked about TOGETHER (round 5.3.1,
        // test5 2026-10-08: one `may` per row read the catalog and the identity
        // table per row; at 160 rps of the login page's list main's
        // `Repos.List` p50 was 2.7 s on a half-idle box).
        let mut ask: Vec<(usize, gunnar_proto::RepoPath)> = Vec::new();
        let mut slots: Vec<Option<gunnar_proto::Repo>> = Vec::new();
        for repo in repos {
            let Some(path) = repo.path.as_ref() else {
                // A catalogue row with no path cannot be authorised, so it is
                // not shown. Dropping it is the only safe reading of a row this
                // code cannot ask a question about.
                continue;
            };
            // **Everything, but a customer's account** (2026-10-05): a row
            // inside an account closed to the caller's server grant is asked
            // about on its own, like any outsider's — a public one is shown.
            if listing == Listing::Everything {
                let closed = match closed_ns.get(&path.namespace) {
                    Some(known) => *known,
                    None => {
                        let known = self.shielded(caller, &path.namespace)?;
                        closed_ns.insert(path.namespace.clone(), known);
                        known
                    }
                };
                if !closed {
                    slots.push(Some(repo));
                    continue;
                }
            }
            ask.push((slots.len(), path.clone()));
            slots.push(Some(repo));
        }
        if !ask.is_empty() {
            let paths: Vec<gunnar_proto::RepoPath> = ask.iter().map(|(_, p)| p.clone()).collect();
            let answers = self
                .control
                .may_each_repo(&caller.principal_id, &paths, GrantLevel::Read)
                .map_err(|err| Status::unavailable(format!("the identity table could not be read: {err}")))?;
            for ((slot, _), may) in ask.iter().zip(answers) {
                if !may {
                    slots[*slot] = None;
                }
            }
        }
        readable.extend(slots.into_iter().flatten());
        Ok(readable)
    }

    /// [`ServiceContext::caller`] then [`ServiceContext::require`], for the RPCs
    /// whose scope is known before the message is parsed.
    pub fn authorize<T>(
        &self,
        request: &Request<T>,
        access: Access,
        level: GrantLevel,
        method: &'static str,
    ) -> Result<Caller, Status> {
        let caller = self.caller(request)?;
        self.require(&caller, access, level, method)?;
        Ok(caller)
    }

    // ── The owner's enterprise list (2026-10-05) ───────────────────────────

    /// **Write `event` into `account`'s audit log** — best effort: a log
    /// that cannot be written is one WARN line, never a refused RPC (the
    /// control plane's own trail makes the same promise, GUN-044).
    pub fn account_event(&self, account: Option<String>, event: crate::enterprise::AccountAuditEvent) {
        let Some(account) = account else {
            return;
        };
        let Some(plane) = self.control.enterprise() else {
            return;
        };
        if let Err(err) = plane.account_audit_record(&account, &event) {
            tracing::warn!(
                target: crate::auth::AUDIT_TARGET,
                event = "account_audit.not_written",
                account = %account,
                kind = %event.kind,
                reason = %err,
                "an event could not be written to its account's audit log"
            );
        }
    }

    /// **The enterprise half of the plane**, or the refusal a bare plane gives.
    pub fn enterprise(&self) -> Result<&dyn crate::enterprise::EnterprisePlane, Status> {
        self.control.enterprise().ok_or_else(|| crate::enterprise::no_plane().into())
    }

    /// The account `namespace` belongs to (a person's), if any.
    pub fn account_of_namespace(&self, namespace: &str) -> Option<String> {
        self.control.enterprise()?.account_of_namespace(namespace).ok().flatten()
    }

    /// **The account that enrolled `principal_id` as a MEMBER**, or `None`
    /// (2026-10-07, the members model). A plane that keeps no employers
    /// answers `None`: nobody is anybody's member there.
    pub fn employer_of(&self, principal_id: &str) -> Result<Option<String>, Status> {
        match self.control.employer_of(principal_id) {
            Ok(employer) => Ok(employer),
            Err(crate::seam::ControlError::Unimplemented(_)) => Ok(None),
            Err(err) => Err(err.into()),
        }
    }

    /// **What `principal_id` is to the account it acts in** (2026-10-07, the
    /// members model: a customer is an ACCOUNT that pays; its members use its
    /// space and never buy) — `Access.WhoAmI`'s `account` / `account_role` /
    /// `account_admin`, read off the identity table and nothing else:
    ///
    /// * a MEMBER (an employer row): the employer's account, `employee`;
    /// * a PERSON whose own namespace exists: that account — `admin` when it
    ///   has enrolled members or holds seats, `individual` otherwise;
    /// * anything else (a machine, a deploy key, a group, a principal with no
    ///   namespace): none — it speaks for no account and buys nothing.
    pub fn standing(&self, principal_id: &str) -> Result<Standing, Status> {
        if let Some(account) = self.employer_of(principal_id)? {
            let admin = self.display_name(&account)?;
            // **An APPOINTED account admin** (owner 2026-10-08): a member the account admin gave
            // Admin over the account's namespace decides for its repositories (publish, encrypt)
            // and reads its audit log — the rule SetVisibility keeps — and never buys
            // (`Billing.MintActorTicket` refuses every member, this one too).
            let appointed = match self.control.may(principal_id, &Access::namespace(&account), gunnar_proto::GrantLevel::Admin) {
                Ok(yes) => yes,
                Err(crate::seam::ControlError::Unimplemented(_)) => false,
                Err(err) => return Err(err.into()),
            };
            return Ok(Standing { account, role: if appointed { "admin-appointed" } else { "employee" }, admin });
        }
        let person = self
            .control
            .principal(principal_id)?
            .is_some_and(|p| p.kind == gunnar_proto::PrincipalKind::User as i32);
        if !person || self.control.namespace(principal_id)?.is_none() {
            return Ok(Standing::default());
        }
        // Seats first (one key), then "has it enrolled anyone" — never the whole listing of its
        // members: this runs on every `Access.WhoAmI` (wiring tracer B 2026-10-08).
        let (seats, _) = self.control.seats(principal_id)?;
        let admin = seats > 0
            || match self.control.has_employees(principal_id) {
                Ok(any) => any,
                Err(crate::seam::ControlError::Unimplemented(_)) => false,
                Err(err) => return Err(err.into()),
            };
        Ok(Standing {
            account: principal_id.to_owned(),
            role: if admin { "admin" } else { "individual" },
            admin: self.display_name(principal_id)?,
        })
    }

    /// A principal's display name, its id when it has none.
    fn display_name(&self, principal_id: &str) -> Result<String, Status> {
        Ok(self
            .control
            .principal(principal_id)?
            .map(|p| p.name)
            .filter(|name| !name.trim().is_empty())
            .unwrap_or_else(|| principal_id.to_owned()))
    }

    /// The account `principal_id` belongs to (its employer, or its own).
    pub fn account_of_principal(&self, principal_id: &str) -> Option<String> {
        self.control.enterprise()?.account_of_principal(principal_id).ok().flatten()
    }

    /// **Is `namespace` closed to the caller's server-scope grants?** Asked
    /// per row by every server-wide listing, so an operator's listing shows
    /// what is not inside a customer's account and nothing that is.
    pub fn shielded(&self, caller: &Caller, namespace: &str) -> Result<bool, Status> {
        if self.config.auth == AuthMode::AllowAllInsecure {
            return Ok(false);
        }
        let Some(plane) = self.control.enterprise() else {
            return Ok(false);
        };
        plane
            .shielded(&caller.principal_id, namespace)
            .map_err(|err| Status::unavailable(format!("the identity table could not be read: {err}")))
    }

    /// `rows` without those inside an account closed to the caller.
    pub fn retain_unshielded<T>(
        &self,
        caller: &Caller,
        rows: Vec<T>,
        namespace_of: impl Fn(&T) -> Option<String>,
    ) -> Result<Vec<T>, Status> {
        let mut kept = Vec::with_capacity(rows.len());
        let mut closed_ns: std::collections::HashMap<String, bool> = std::collections::HashMap::new();
        for row in rows {
            let Some(ns) = namespace_of(&row) else {
                kept.push(row);
                continue;
            };
            let closed = match closed_ns.get(&ns) {
                Some(known) => *known,
                None => {
                    let known = self.shielded(caller, &ns)?;
                    closed_ns.insert(ns, known);
                    known
                }
            };
            if !closed {
                kept.push(row);
            }
        }
        Ok(kept)
    }

    /// **The name filter, at a door that makes a name**: clean passes; a
    /// borderline name is held for the account admin (and refused for now);
    /// a blocked or reserved one is refused and noted for the account admin.
    /// `exempt_reserved`: a server admin naming a namespace or a machine.
    /// Every refusal is in the account's audit log.
    pub fn filter_name(
        &self,
        caller: &Caller,
        kind: &str,
        name: &str,
        account: Option<String>,
        exempt_reserved: bool,
    ) -> Result<(), Status> {
        let Some(plane) = self.control.enterprise() else {
            return Ok(());
        };
        let in_account = account.clone().unwrap_or_default();
        let verdict = plane.name_check(kind, name, &in_account)?;
        if verdict.is_clean() || (exempt_reserved && verdict.is_reserved()) {
            return Ok(());
        }
        // A borderline name with nobody to review it (a new account's own
        // name) is the operator's to allow: a server admin's passes.
        if verdict.is_borderline() && account.is_none() && exempt_reserved {
            return Ok(());
        }
        // Held only where there is an account admin to ask; a new account's
        // own borderline name has nobody, so it is refused.
        let hold = verdict.is_borderline() && account.is_some();
        let sentence = if verdict.is_borderline() && !hold {
            format!(
                "the {kind} name {name:?} needs a person's yes and there is no account admin to ask yet; \
                 choose another name"
            )
        } else {
            verdict.sentence.clone()
        };
        if let Some(account) = account.as_deref() {
            if let Err(err) = plane.name_review_add(kind, name, account, &caller.principal_id, hold) {
                tracing::warn!(
                    target: crate::auth::AUDIT_TARGET,
                    event = "name_filter.not_queued",
                    account,
                    reason = %err,
                    "a name the filter stopped could not be put in its account's queue"
                );
            }
        }
        let kind_word = if hold { "name.held" } else { "name.refused" };
        self.account_event(
            account,
            crate::enterprise::AccountAuditEvent::by(caller, kind_word, name)
                .detail(format!("a {kind} name, {} by the name filter", verdict.verdict))
                .refused(),
        );
        tracing::info!(
            target: crate::auth::AUDIT_TARGET,
            event = "name_filter.stopped",
            principal = %caller.principal_id,
            kind,
            verdict = %verdict.verdict,
            "the name filter stopped a name"
        );
        Err(if hold {
            Status::failed_precondition(sentence)
        } else {
            Status::invalid_argument(sentence)
        })
    }
}

macro_rules! service {
    ($name:ident, $doc:literal) => {
        #[doc = $doc]
        #[derive(Clone)]
        pub struct $name {
            pub(crate) ctx: ServiceContext,
        }

        impl $name {
            pub fn new(ctx: ServiceContext) -> Self {
                Self { ctx }
            }
        }

        impl From<ServiceContext> for $name {
            fn from(ctx: ServiceContext) -> Self {
                Self::new(ctx)
            }
        }
    };
}

service!(ReposService, "Namespaces and repositories: the catalogue.");
service!(
    AccountsService,
    "The per-USER verbs: the account gate, and the hard purge that empties an \
     account. `Repos` reclaims one repository at a time; this one takes a \
     user's whole namespace, and locks them out while it does."
);
service!(
    ClansService,
    "Clans (2026-10-05): ordinary accounts grouped in a clan with a namespace \
     of its own, each member giving it part of its own room; what is made in \
     it is administered by the clan's admin."
);
service!(
    RefsService,
    "Refs, commit log, object reads, diffs. Read-only."
);
service!(AccessService, "Principals, public keys and grants (the ACL model).");
service!(
    SsoService,
    "Company single sign-on: an account's own identity provider (OIDC, SAML 2.0), \
     SCIM 2.0 provisioning and the CSV import. Its people are EMPLOYEES of the \
     account, created new or provisioned, never an attached principal."
);
service!(PolicyService, "Branch protection and ref-name policy (the pre-receive gate).");
service!(
    EnterpriseService,
    "The owner's enterprise list: the account admin's audit log, the operator's \
     aggregates and the look the account admin grants, the name filter's lists \
     and the account admin's review queue. Every account has it."
);
service!(
    EventsService,
    "`Watch`, the server-streaming event feed (the event stream)."
);
service!(
    AdminService,
    "Health, stats, storage backend info, and `Capacity` — \"could you serve a \
     cap of N bytes?\", and how much storage the box has."
);
#[cfg(feature = "metered")]
service!(
    EntitlementService,
    "The paid state per namespace, written by monetize over `Set` and read by \
     `authorise_service`. The METERED appliance's service: compiled only with \
     the `metered` feature, and absent — not stubbed — without it."
);
#[cfg(feature = "metered")]
service!(
    SealedService,
    "A named sealed slot: bytes under a name, sealed under the KEK and logged \
     as a `Secret` frame (the twin, snapshots and a recharge carry it), read \
     and written only by the machine principal that owns it. The METERED \
     appliance's, absent and not stubbed without the feature."
);
#[cfg(feature = "metered")]
service!(
    BillingService,
    "The tenant's own renewal path: the appliance's word, signed, that ONE \
     human may act for ONE tenant — checked against the identity table here \
     rather than by the console, which holds a server-wide grant. The METERED \
     appliance's second service, absent and not stubbed without the feature; \
     see `service::billing` for why the scope check cannot live upstream."
);
service!(
    UsageService,
    "Per-namespace meters (`pack_bytes`, `cache_bytes`, `lfs_bytes`, …): the \
     numbers every storage report is made of, served to self-hosters and operators \
     alike. Open source, always on."
);
service!(
    UsageBatchService,
    "Many namespaces' meters in one call, optionally only the ones that changed \
     (`UsageBatch.ReadMany`, 2026-10-06): what a usage poller reads instead of \
     one `Usage.Read` per namespace. Open source, always on."
);
service!(
    ForgeService,
    "Issues, change proposals and review, as signed append-only events \
     (roadmap phase 1). The server never signs; it verifies and appends."
);
service!(MirrorService, "Pull-mirroring a remote forge (mirroring).");
service!(
    WebhooksService,
    "Outbound webhook delivery of event-stream events to external URLs, signed with an \
     sshsig the receiver verifies against the server's published key."
);
service!(
    PullsService,
    "Pull requests: records in the control store, merges fast-forward-only \
     through the store's own ref CAS."
);
service!(
    BootstrapService,
    "First-boot enrolment. **The one service served without a credential**, and \
     only while nothing has ever been enrolled. It is mounted OUTSIDE \
     [`crate::auth::AuthGate`]; see [`bootstrap`] for why that is safe and what \
     holds it to two methods."
);

/// ★ **f-principal-reach (money 2026-09-28): a NARROWED principal.** The
/// primary enrolled monetize as a server-wide `machine admin`; a CENTRAL
/// monetize serving other owners' estates must not hold that. A narrowed
/// principal has no grant at all and may call exactly the methods its list
/// names — the ones monetize's gunnar plugin calls ([`crate::BILLING_METHODS`])
/// — and nothing else, by name.
#[cfg(test)]
mod narrowed_tests {
    use super::*;
    use crate::seam::MemoryControlPlane;

    fn caller(id: &str) -> Caller {
        Caller { principal_id: id.into(), fingerprint: "SHA256:x".into(), machine: true, authenticated: true }
    }

    #[test]
    fn a_narrowed_principal_may_call_its_list_and_nothing_else() {
        let plane = Arc::new(MemoryControlPlane::new());
        plane.narrow("monetize-central", crate::BILLING_METHODS);
        let ctx = ServiceContext::new(plane.clone(), Arc::new(crate::bus::EventBus::new(8)), crate::ServerConfig::default());
        let c = caller("monetize-central");
        for m in ["Entitlement.Set", "Admin.Capacity", "Admin.GrowStart", "Usage.ReadAll"] {
            let m: &'static str = crate::BILLING_METHODS.iter().copied().find(|x| *x == m).expect("on the list");
            ctx.require(&c, Access::Server, GrantLevel::Admin, m).unwrap_or_else(|e| panic!("{m}: {e:?}"));
        }
        for m in ["Admin.SetTunnelAddress", "Access.AddKey", "Mirror.SetRole", "Admin.IssueTwinCertificate"] {
            let e = ctx.require(&c, Access::Server, GrantLevel::Admin, m).unwrap_err();
            assert_eq!(e.code(), tonic::Code::PermissionDenied, "{m}");
            assert!(e.message().contains("narrowed"), "{m}: {}", e.message());
        }
        // A principal that is NOT narrowed is judged by its grants as before.
        crate::seam::ControlPlane::add_principal(plane.as_ref(), "rickard", "rickard", gunnar_proto::PrincipalKind::User).unwrap();
        plane.grant_server("rickard", GrantLevel::Admin);
        ctx.require(&caller("rickard"), Access::Server, GrantLevel::Admin, "Admin.SetTunnelAddress").expect("an admin is an admin");
    }
}