Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar

crates/gunnar-grpc/src/service/access.rs

//! `Access`: principals, public keys and grants (the ACL model).
//!
//! This is the service authentication matters most for. Every RPC here reads
//! or mutates who may read or write which repository over SSH. It is how the
//! key table the SSH listener authenticates against is *written*, so every one
//! of them takes **Admin**, and the identity-table reads take Admin too: a
//! principal list and a key list are the reconnaissance half of the same
//! attack.
//!
//! `Grant` and `Revoke` scope to what they hand out: a namespace grant needs
//! Admin on that namespace, a repo grant needs Admin on that repo. A tenant
//! administrator can therefore manage their own tenant without being a server
//! administrator (multi-tenancy).
//!
//! **The self-scope** (owner, 2026-10-04): a principal manages its OWN keys —
//! `AddKey`, `ListKeys` and `RevokeKey` on itself — with no grant at all
//! (`ServiceContext::require_self`). Everything about anybody else is still a
//! server Admin's.

use gunnar_proto::access_server::Access;
use gunnar_proto::{
    AddCredentialRequest, AddCredentialResponse, AddGroupMemberRequest, AddGroupMemberResponse,
    AddKeyRequest, AddKeyResponse, AddPrincipalRequest, AddPrincipalResponse,
    AddUnlockMethodRequest, AddUnlockMethodResponse, ApproveMachineEnrolmentRequest,
    ApproveMachineEnrolmentResponse, CheckRequest, CheckResponse, CreateEncryptionKeyRequest,
    CreateEncryptionKeyResponse, GetEncryptionKeyRequest, GetEncryptionKeyResponse, GrantLevel,
    GrantRequest, GrantResponse, ListCredentialsRequest, ListCredentialsResponse,
    ListGrantsRequest, ListGrantsResponse, ListGroupMembersRequest, ListGroupMembersResponse,
    ListGroupMembershipEventsRequest, ListGroupMembershipEventsResponse, ListKeysRequest,
    ListKeysResponse, ListMachineEnrolmentsRequest, ListMachineEnrolmentsResponse,
    ListPrincipalsRequest, ListPrincipalsResponse, MachineEnrolmentStatusRequest,
    MachineEnrolmentStatusResponse, MembershipAction, PrincipalKind, RefuseMachineEnrolmentRequest,
    RefuseMachineEnrolmentResponse, RemoveGroupMemberRequest, RemoveGroupMemberResponse,
    RemovePrincipalRequest, RemovePrincipalResponse, RemoveUnlockMethodRequest,
    RemoveUnlockMethodResponse, RenameCredentialRequest, RenameCredentialResponse,
    RenamePrincipalRequest, RenamePrincipalResponse, RequestMachineEnrolmentRequest,
    RequestMachineEnrolmentResponse, RevokeCredentialRequest, RevokeCredentialResponse,
    RevokeKeyRequest, RevokeKeyResponse, RevokeRequest, RevokeResponse, SignedMembershipEvent,
    WhoAmIRequest, WhoAmIResponse,
};
use tonic::{Request, Response, Status};

use crate::auth::{Access as Scope, Caller};
use crate::service::AccessService;
use crate::status::opt_str;

/// **The names machines sign their own calls under** (Stage L, 2026-10-04):
/// `console-*` (the console's per-user principals of old), `gunnar-ui` (the
/// console itself) and `uitest-console` (its test rig's) — and, since
/// 2026-10-05, `kit-*`: the machine an approved enrolment makes for an ops
/// kit (`Access.ApproveMachineEnrolment`). A person, a group or a deploy key
/// may never carry one, as its id or as its name — otherwise an account admin
/// enrolls `console-<x>` (or `kit-<x>`) and whatever trusts the name trusts
/// them. Compared case-insensitively. A MACHINE principal may: the console's
/// own is made exactly so, by a server admin, and a kit's by an approval.
pub fn is_reserved_principal_name(text: &str) -> bool {
    let text = text.trim().to_ascii_lowercase();
    text.starts_with("console-")
        || text.starts_with(crate::machine_enrolment::MACHINE_PREFIX)
        || text == "gunnar-ui"
        || text == "uitest-console"
}

/// The refusal for [`is_reserved_principal_name`], naming the rule.
pub fn reserved_name_refusal(text: &str, what: &str) -> Status {
    Status::invalid_argument(format!(
        "{text:?} is a reserved name — `console-*`, `gunnar-ui` and `uitest-console` are what a \
         console signs its own calls under, and `kit-*` what an approved ops kit does — so \
         {what} may not carry it; only a machine principal made by a server admin (or an \
         approval) may"
    ))
}

/// The audit line for every request a sweep found expired.
fn audit_expired(expired: crate::machine_enrolment::Expired) {
    for row in expired {
        tracing::info!(
            target: crate::auth::AUDIT_TARGET,
            event = "access.machine_enrolment_expired",
            request = %row.request_id,
            machine = %row.machine,
            code = %row.code,
            "a machine enrolment request expired with nobody's approval"
        );
    }
}

/// The role a request asks for, or an approver gives: ADMIN, a console's
/// operator (WRITE), or READ. **A request that names none asks for the
/// lowest, READ** (owner order 2026-10-18): the asking door needs no login,
/// so its default must never be a power; the approving admin grants more.
fn machine_level(level: i32) -> Result<GrantLevel, Status> {
    if level == GrantLevel::Unspecified as i32 {
        return Ok(GrantLevel::Read);
    }
    GrantLevel::try_from(level)
        .ok()
        .filter(|l| matches!(l, GrantLevel::Admin | GrantLevel::Write | GrantLevel::Read))
        .ok_or_else(|| {
            Status::invalid_argument(
                "`server_level`: ADMIN, WRITE (a console's operator) or READ — a machine credential \
                 is asked for to administer, operate or read the box",
            )
        })
}

/// The longest a web session key lives: thirty days.
pub const WEB_SESSION_MAX_MS: i64 = 30 * 86_400_000;

/// A web ceremony gunnar would not accept, as the refusal the relay gets —
/// and one WARN line, because a refused passkey is what an operator looks
/// for after "I cannot log in".
fn ceremony_refused(method: &'static str, why: String) -> Status {
    tracing::warn!(
        target: crate::auth::AUDIT_TARGET,
        event = "access.web_ceremony_refused",
        method,
        reason = %why,
        "a web ceremony was refused"
    );
    Status::permission_denied(format!("{method} refused: {why}"))
}

/// How long a first-login link lives when nobody says: seven days.
pub const INVITE_DEFAULT_MS: i64 = 7 * 86_400_000;
/// The longest a link may live: thirty days.
pub const INVITE_MAX_MS: i64 = 30 * 86_400_000;

impl AccessService {
    /// **A34-24: `require_sso` on a credential an employee mints for
    /// themself** ([`crate::sso::mint_refused`]): from the session key of a
    /// sign-in through the IdP, or the identity-core session such a sign-in
    /// minted (its persisted admission, [`crate::identity::GunnarStore::sso_admitted_fingerprint`]), yes; from any other key of
    /// theirs (an SSH key, a passkey login's core session: both bypass the
    /// IdP), refused by name and audited. An admin's mint
    /// for somebody else is the admin's word and is not asked here.
    fn refuse_a_mint_past_the_idp(
        &self,
        caller: &Caller,
        method: &'static str,
    ) -> Result<(), Status> {
        let Some(plane) = self.ctx.control.sso() else {
            return Ok(());
        };
        let required = plane.passkey_login_refused(&caller.principal_id)?;
        if required.is_none() {
            return Ok(());
        }
        // An identity-core session (`session:…`) is the IdP's only when the sign-in
        // through it vouched for it; a passkey login's core session bypasses the IdP.
        let on_idp_session = if caller.fingerprint.starts_with("session:") {
            // RA5-38: the persisted admission, the one record the gate reads too.
            self.ctx.identity.as_ref().is_some_and(|i| {
                i.host()
                    .gunnar_store()
                    .sso_admitted_fingerprint(&caller.fingerprint)
            })
        } else {
            self.ctx
                .control
                .list_keys(Some(&caller.principal_id))?
                .iter()
                .any(|k| {
                    k.fingerprint == caller.fingerprint
                        && crate::sso::is_session_key(
                            k.added_at_unix_ms,
                            k.expires_at_unix_ms,
                            WEB_SESSION_MAX_MS,
                        )
                })
        };
        match crate::sso::mint_refused(required, on_idp_session) {
            None => Ok(()),
            Some(why) => {
                crate::auth::audit_denied(
                    method,
                    caller,
                    &Scope::Server,
                    GrantLevel::Admin,
                    self.ctx.config.auth,
                    &why,
                    self.ctx.ledger(),
                );
                Err(Status::permission_denied(why))
            }
        }
    }

    /// **Rows of people inside an account closed to the caller's server
    /// grant, dropped** (2026-10-05, the operator sees aggregates only).
    /// `owners_shown`: the account's own person is kept — its id is the
    /// account's name, which the operator addresses to gate, bill or recover
    /// it; what is hidden is the account's MEMBERS (its employees).
    fn members_hidden<T>(
        &self,
        caller: &Caller,
        rows: Vec<T>,
        principal_of: impl Fn(&T) -> String,
        owners_shown: bool,
    ) -> Result<Vec<T>, Status> {
        if self.ctx.control.enterprise().is_none() {
            return Ok(rows);
        }
        let mut kept = Vec::with_capacity(rows.len());
        let mut known: std::collections::HashMap<String, bool> = std::collections::HashMap::new();
        for row in rows {
            let principal = principal_of(&row);
            let hidden = match known.get(&principal) {
                Some(hidden) => *hidden,
                None => {
                    let hidden = match self.ctx.account_of_principal(&principal) {
                        Some(account) if owners_shown && account == principal => false,
                        Some(account) => self.ctx.shielded(caller, &account)?,
                        None => false,
                    };
                    known.insert(principal, hidden);
                    hidden
                }
            };
            if !hidden {
                kept.push(row);
            }
        }
        Ok(kept)
    }

    /// **The box keeps one admin who can log in.** Refuse to revoke a passkey
    /// when it is the last one held by any principal with a server-wide Admin
    /// — the rule the console kept in its own store until passkeys came to
    /// gunnar. A passkey of a principal who is not an admin is never held
    /// back by this.
    fn refuse_the_last_admin_passkey(&self, credential_id: &[u8]) -> Result<(), Status> {
        let Some(stored) = self.ctx.control.credential(credential_id)? else {
            return Ok(());
        };
        let is_admin = |principal: &str| {
            self.ctx
                .control
                .may(principal, &Scope::Server, GrantLevel::Admin)
                .map_err(|err| {
                    Status::unavailable(format!("the identity table could not be read: {err}"))
                })
        };
        if !is_admin(&stored.credential.principal_id)? {
            return Ok(());
        }
        let mut admins: std::collections::BTreeMap<String, bool> =
            std::collections::BTreeMap::new();
        for other in self.ctx.control.credentials(None)? {
            if other.credential_id == stored.credential.credential_id {
                continue;
            }
            let admin = match admins.get(&other.principal_id) {
                Some(known) => *known,
                None => {
                    let known = is_admin(&other.principal_id)?;
                    admins.insert(other.principal_id.clone(), known);
                    known
                }
            };
            if admin {
                return Ok(());
            }
        }
        Err(Status::failed_precondition(format!(
            "this is the last passkey any server admin holds ({}'s): the box must keep one admin \
             who can log in — add another passkey, or another admin, first",
            stored.credential.principal_id
        )))
    }
}

#[tonic::async_trait]
impl Access for AccessService {
    /// **A first-login link onto an existing person, minted by the identity
    /// core** on a server admin's authority (the console relaying a paid
    /// sign-up, an operator's CLI). Only a person; `first_only` refuses one who
    /// holds a passkey. The secret is the core's, shown here once.
    async fn enrolment_link(
        &self,
        request: Request<gunnar_proto::EnrolmentLinkRequest>,
    ) -> Result<Response<gunnar_proto::EnrolmentLinkResponse>, Status> {
        const METHOD: &str = "Access.EnrolmentLink";
        let caller = self
            .ctx
            .authorize(&request, Scope::Server, GrantLevel::Admin, METHOD)?;
        let request = request.into_inner();
        let principal_id = request.principal_id.trim();
        let identity = self.ctx.identity.clone().ok_or_else(|| {
            Status::unavailable("this server serves no identity core, so it mints no link")
        })?;
        let person = self
            .ctx
            .control
            .principal(principal_id)?
            .ok_or_else(|| Status::not_found(format!("no principal {principal_id}")))?;
        if person.kind != gunnar_proto::PrincipalKind::User as i32 {
            return Err(Status::failed_precondition(format!(
                "{principal_id} is not a person: a link enrols a person's passkey"
            )));
        }
        if request.first_only && !self.ctx.control.credentials(Some(principal_id))?.is_empty() {
            return Err(Status::failed_precondition(format!(
                "{principal_id} already holds a passkey: sign in with it, or add one from a signed-in device"
            )));
        }
        let now = crate::bus::now_unix_ms();
        let expires = match request.expires_at_unix_ms {
            0 => now + INVITE_DEFAULT_MS,
            at if at <= now => {
                return Err(Status::invalid_argument(
                    "`expires_at_unix_ms` is in the past",
                ))
            }
            at if at > now + INVITE_MAX_MS => {
                return Err(Status::invalid_argument("a link lives at most thirty days"));
            }
            at => at,
        };
        let made = identity
            .enrolment(
                principal_id,
                &caller.principal_id,
                Some((expires - now).div_euclid(1000)),
            )
            .map_err(crate::identity::status_of)?;
        tracing::info!(
            target: crate::auth::AUDIT_TARGET,
            event = "access.enrolment_link",
            principal = %principal_id,
            by = %caller.principal_id,
            invite = %made.invite_id,
            first_only = request.first_only,
            "the identity core minted a first-login link onto an existing person"
        );
        Ok(Response::new(gunnar_proto::EnrolmentLinkResponse {
            secret: made.secret,
            invite_id: made.invite_id,
            expires_at_unix_ms: made.expires_at.saturating_mul(1000),
        }))
    }

    /// A fresh challenge for a machine's enrolment ceremony: the relay's.
    async fn machine_challenge(
        &self,
        request: Request<gunnar_proto::MachineChallengeRequest>,
    ) -> Result<Response<gunnar_proto::MachineChallengeResponse>, Status> {
        self.ctx.authorize(
            &request,
            Scope::Server,
            GrantLevel::Admin,
            "Access.MachineChallenge",
        )?;
        let client = request.get_ref().client.trim();
        let client = (!client.is_empty()).then(|| crate::webauthn::ClientKey::of_peer(client));
        let (challenge, expires_at_unix_ms) = self
            .ctx
            .web_challenges
            .issue(client.as_ref(), crate::bus::now_unix_ms())
            .map_err(|full| Status::resource_exhausted(full.to_string()))?;
        Ok(Response::new(gunnar_proto::MachineChallengeResponse {
            challenge,
            expires_at_unix_ms,
        }))
    }

    async fn add_principal(
        &self,
        request: Request<AddPrincipalRequest>,
    ) -> Result<Response<AddPrincipalResponse>, Status> {
        let caller = self.ctx.authorize(
            &request,
            Scope::Server,
            GrantLevel::Admin,
            "Access.AddPrincipal",
        )?;
        let request = request.into_inner();
        let kind = PrincipalKind::try_from(request.kind)
            .map_err(|_| Status::invalid_argument("unknown principal kind"))?;
        if kind == PrincipalKind::Unspecified {
            return Err(Status::invalid_argument(
                "`kind` is required: USER, MACHINE, DEPLOY or GROUP",
            ));
        }
        if request.name.is_empty() {
            return Err(Status::invalid_argument("`name` is required"));
        }
        // An empty `id` means the name, which is what every caller from before
        // the field existed asked for. The id is the account, so a caller that
        // sets it is choosing an account slug that is not the display name.
        let id = if request.id.is_empty() {
            request.name.as_str()
        } else {
            request.id.as_str()
        };
        // A console's name is a machine's. The caller is a server admin here
        // already (the line above), so that is the whole exception.
        if kind != PrincipalKind::Machine {
            for text in [id, request.name.as_str()] {
                if is_reserved_principal_name(text) {
                    return Err(reserved_name_refusal(
                        text,
                        "a person, a group or a deploy key",
                    ));
                }
            }
        }
        // The name filter (2026-10-05): a person's id is an account's name.
        // Reserved names are a machine's to carry, and the operator's (a
        // PERSON with server Admin) to hand out; a machine relaying somebody's
        // choice is held to them.
        // Reserved names are a PERSON's to be refused: a machine, a group or a
        // deploy key named `twin` or `kit-…` is gunnar's own machinery.
        let what = if kind == PrincipalKind::User {
            "account"
        } else {
            "member"
        };
        for text in [id, request.name.as_str()] {
            self.ctx.filter_name(
                &caller,
                what,
                text,
                None,
                kind != PrincipalKind::User || !caller.machine,
            )?;
        }
        let principal = self.ctx.control.add_principal(id, &request.name, kind)?;
        self.ctx.account_event(
            self.ctx.account_of_principal(&principal.id),
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "account.created",
                principal.id.clone(),
            ),
        );
        Ok(Response::new(AddPrincipalResponse {
            principal: Some(principal),
        }))
    }

    async fn list_principals(
        &self,
        request: Request<ListPrincipalsRequest>,
    ) -> Result<Response<ListPrincipalsResponse>, Status> {
        let caller = self.ctx.authorize(
            &request,
            Scope::Server,
            GrantLevel::Admin,
            "Access.ListPrincipals",
        )?;
        let request = request.into_inner();
        // A name filter is one probe on the name index, not a listing that is
        // filtered afterwards: the console asks this on every registration.
        let principals = match opt_str(&request.name) {
            Some(name) => self
                .ctx
                .control
                .principal_by_name(name)?
                .into_iter()
                .collect(),
            None => self.ctx.control.list_principals()?,
        };
        let principals = self.members_hidden(&caller, principals, |p| p.id.clone(), true)?;
        // **Last sign-in and last operation** (2026-10-21), for the principals
        // this answer names and no others, behind the same admin-only gate as
        // the listing: nobody reads another person's stamps without it.
        let seen = self.ctx.control.principal_activity()?;
        let mut last_sign_in_unix_ms = std::collections::HashMap::new();
        let mut last_operation_unix_ms = std::collections::HashMap::new();
        for p in &principals {
            if let Some(s) = seen.get(&p.id) {
                if let Some(at) = s.last_sign_in_unix_ms {
                    last_sign_in_unix_ms.insert(p.id.clone(), at);
                }
                if let Some(at) = s.last_operation_unix_ms {
                    last_operation_unix_ms.insert(p.id.clone(), at);
                }
            }
        }
        Ok(Response::new(ListPrincipalsResponse {
            principals,
            next_page_token: String::new(),
            last_sign_in_unix_ms,
            last_operation_unix_ms,
        }))
    }

    async fn rename_principal(
        &self,
        request: Request<RenamePrincipalRequest>,
    ) -> Result<Response<RenamePrincipalResponse>, Status> {
        // Same gate as AddPrincipal: a name is what an operator reads in a
        // listing, a grant prompt or an audit line, and letting a non-admin
        // choose one is letting them choose what an operator sees.
        let caller = self.ctx.authorize(
            &request,
            Scope::Server,
            GrantLevel::Admin,
            "Access.RenamePrincipal",
        )?;
        let request = request.into_inner();
        if request.principal_id.is_empty() || request.name.is_empty() {
            return Err(Status::invalid_argument(
                "`principal_id` and `name` are both required",
            ));
        }
        // The same door as AddPrincipal: a person is not renamed into a
        // console's name; a machine may be.
        if is_reserved_principal_name(&request.name) {
            let kind = self
                .ctx
                .control
                .principal(&request.principal_id)?
                .map(|p| p.kind)
                .unwrap_or_default();
            if kind != PrincipalKind::Machine as i32 {
                return Err(reserved_name_refusal(
                    &request.name,
                    "a person, a group or a deploy key",
                ));
            }
        }
        let account = self.ctx.account_of_principal(&request.principal_id);
        let target_kind = self
            .ctx
            .control
            .principal(&request.principal_id)?
            .map(|p| p.kind)
            .unwrap_or_default();
        let what = if account.as_deref() == Some(request.principal_id.as_str()) {
            "account"
        } else {
            "member"
        };
        self.ctx.filter_name(
            &caller,
            what,
            &request.name,
            account.clone(),
            target_kind != PrincipalKind::User as i32 || !caller.machine,
        )?;
        let principal = self
            .ctx
            .control
            .rename_principal(&request.principal_id, &request.name)?;
        self.ctx.account_event(
            account,
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "member.renamed",
                principal.id.clone(),
            )
            .detail(format!("now called {:?}", principal.name)),
        );
        Ok(Response::new(RenamePrincipalResponse {
            principal: Some(principal),
        }))
    }

    async fn remove_principal(
        &self,
        request: Request<RemovePrincipalRequest>,
    ) -> Result<Response<RemovePrincipalResponse>, Status> {
        let caller = self.ctx.authorize(
            &request,
            Scope::Server,
            GrantLevel::Admin,
            "Access.RemovePrincipal",
        )?;
        let request = request.into_inner();
        let account = self.ctx.account_of_principal(&request.principal_id);
        let (keys_revoked, grants_revoked) =
            self.ctx.control.remove_principal(&request.principal_id)?;
        self.ctx.account_event(
            account,
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "member.removed",
                request.principal_id.clone(),
            )
            .detail(format!(
                "{keys_revoked} keys and {grants_revoked} grants with it"
            )),
        );
        Ok(Response::new(RemovePrincipalResponse {
            keys_revoked,
            grants_revoked,
        }))
    }

    async fn add_key(
        &self,
        request: Request<AddKeyRequest>,
    ) -> Result<Response<AddKeyResponse>, Status> {
        // Adding a key IS granting SSH access. Nothing below Admin — for
        // anybody ELSE's principal. On the caller's own (the self-scope,
        // 2026-10-04) no grant is asked: the call is already that principal's
        // own signed word, so no attestation is owed either
        // (`key_attestation::Requirement::CallerIsTarget`).
        let caller = self.ctx.caller(&request)?;
        if self.ctx.is_self(&caller, &request.get_ref().principal_id) {
            self.ctx
                .require_self(&caller, GrantLevel::Admin, "Access.AddKey")?;
            self.refuse_a_mint_past_the_idp(&caller, "Access.AddKey")?;
        } else {
            self.ctx
                .require(&caller, Scope::Server, GrantLevel::Admin, "Access.AddKey")?;
        }
        let request = request.into_inner();
        // The caller's credential proves who asked. The attestation, checked
        // behind the seam against the target's own keys, proves the person
        // whose access grows agreed — required exactly when there is somebody
        // to ask and the caller is not them (`key_attestation::Requirement`).
        let key = self.ctx.control.add_key(
            &request.principal_id,
            &request.openssh_public_key,
            &request.comment,
            request.expires_at_unix_ms,
            &caller.principal_id,
            request.attestation,
        )?;
        self.ctx.account_event(
            self.ctx.account_of_principal(&request.principal_id),
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "credential.added",
                request.principal_id.clone(),
            )
            .detail(format!("SSH key {}", key.fingerprint)),
        );
        Ok(Response::new(AddKeyResponse { key: Some(key) }))
    }

    async fn revoke_key(
        &self,
        request: Request<RevokeKeyRequest>,
    ) -> Result<Response<RevokeKeyResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        let request = request.into_inner();
        if request.fingerprint.is_empty() {
            return Err(Status::invalid_argument("`fingerprint` is required"));
        }
        // **A key that is the caller's own is the caller's to revoke** (the
        // self-scope, 2026-10-04) — the one it is signing with included: the
        // call is already authenticated, and losing the NEXT call is what was
        // asked for. Any other fingerprint — somebody else's, or none at all —
        // is a server Admin's, and is refused in the same words either way, so
        // the refusal is no oracle for whose a key is.
        let own = caller.authenticated
            && self
                .ctx
                .control
                .list_keys(Some(&caller.principal_id))?
                .iter()
                .any(|key| key.fingerprint == request.fingerprint);
        if own && self.ctx.is_self(&caller, &caller.principal_id) {
            self.ctx
                .require_self(&caller, GrantLevel::Admin, "Access.RevokeKey")?;
        } else {
            self.ctx.require(
                &caller,
                Scope::Server,
                GrantLevel::Admin,
                "Access.RevokeKey",
            )?;
        }
        let holder = self
            .ctx
            .control
            .registered_key(&request.fingerprint)
            .ok()
            .flatten()
            .map(|k| k.principal_id);
        self.ctx.control.revoke_key(&request.fingerprint)?;
        if let Some(holder) = holder {
            self.ctx.account_event(
                self.ctx.account_of_principal(&holder),
                crate::enterprise::AccountAuditEvent::by(
                    &caller,
                    "credential.removed",
                    holder.clone(),
                )
                .detail(format!("SSH key {}", request.fingerprint)),
            );
        }
        Ok(Response::new(RevokeKeyResponse {}))
    }

    async fn list_keys(
        &self,
        request: Request<ListKeysRequest>,
    ) -> Result<Response<ListKeysResponse>, Status> {
        // The whole table, or another principal's keys: a server Admin's (the
        // reconnaissance half). The caller's OWN keys: theirs (the self-scope,
        // 2026-10-04).
        let caller = self.ctx.caller(&request)?;
        if self.ctx.is_self(&caller, &request.get_ref().principal_id) {
            self.ctx
                .require_self(&caller, GrantLevel::Read, "Access.ListKeys")?;
        } else {
            self.ctx
                .require(&caller, Scope::Server, GrantLevel::Admin, "Access.ListKeys")?;
        }
        let request = request.into_inner();
        let keys = self.ctx.control.list_keys(opt_str(&request.principal_id))?;
        let keys = self.members_hidden(&caller, keys, |k| k.principal_id.clone(), true)?;
        Ok(Response::new(ListKeysResponse { keys }))
    }

    async fn grant(
        &self,
        request: Request<GrantRequest>,
    ) -> Result<Response<GrantResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        let request = request.into_inner();
        self.ctx.require(
            &caller,
            grant_scope(&request.namespace, &request.repo),
            GrantLevel::Admin,
            "Access.Grant",
        )?;
        let level = GrantLevel::try_from(request.level)
            .map_err(|_| Status::invalid_argument("unknown grant level"))?;
        let grant = self.ctx.control.grant(
            &request.principal_id,
            &request.namespace,
            &request.repo,
            level,
        )?;
        self.ctx.account_event(
            self.ctx.account_of_namespace(&request.namespace),
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "grant.added",
                request.principal_id.clone(),
            )
            .detail(format!(
                "{} on {}{}",
                crate::auth::level_name(level),
                request.namespace,
                if request.repo.is_empty() {
                    String::new()
                } else {
                    format!("/{}", request.repo)
                }
            )),
        );
        Ok(Response::new(GrantResponse { grant: Some(grant) }))
    }

    async fn revoke(
        &self,
        request: Request<RevokeRequest>,
    ) -> Result<Response<RevokeResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        let request = request.into_inner();
        self.ctx.require(
            &caller,
            grant_scope(&request.namespace, &request.repo),
            GrantLevel::Admin,
            "Access.Revoke",
        )?;
        self.ctx
            .control
            .revoke(&request.principal_id, &request.namespace, &request.repo)?;
        self.ctx.account_event(
            self.ctx.account_of_namespace(&request.namespace),
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "grant.revoked",
                request.principal_id.clone(),
            )
            .detail(format!(
                "on {}{}",
                request.namespace,
                if request.repo.is_empty() {
                    String::new()
                } else {
                    format!("/{}", request.repo)
                }
            )),
        );
        Ok(Response::new(RevokeResponse {}))
    }

    async fn list_grants(
        &self,
        request: Request<ListGrantsRequest>,
    ) -> Result<Response<ListGrantsResponse>, Status> {
        // Server-scoped even when the request filters: the filters are hints,
        // and answering a narrower question with a wider query is how an ACL
        // read turns into an ACL leak.
        //
        // **One exception, and it is not a filter used as a hint** (2026-10-07,
        // the members model): the ACCOUNT ADMIN reads the grants inside their
        // own account — who may read and write its repositories — so they can
        // give a member Write and take it away (`Grant` / `Revoke`, Admin on
        // the repository, which their account already gives them). The
        // namespace is then the QUERY, not a hint: the plane is asked for that
        // namespace's rows only, and the caller must hold Admin on it.
        let caller = self.ctx.caller(&request)?;
        let namespace = request.get_ref().namespace.trim().to_owned();
        if !namespace.is_empty()
            && self
                .ctx
                .check(&caller, Scope::Server, GrantLevel::Admin)?
                .is_some()
        {
            self.ctx.require(
                &caller,
                Scope::namespace(&namespace),
                GrantLevel::Admin,
                "Access.ListGrants",
            )?;
            let request = request.into_inner();
            let grants = self.ctx.control.list_grants(
                opt_str(&request.principal_id),
                Some(&namespace),
                opt_str(&request.repo),
            )?;
            return Ok(Response::new(ListGrantsResponse { grants }));
        }
        self.ctx.require(
            &caller,
            Scope::Server,
            GrantLevel::Admin,
            "Access.ListGrants",
        )?;
        let request = request.into_inner();
        let grants = self.ctx.control.list_grants(
            opt_str(&request.principal_id),
            opt_str(&request.namespace),
            opt_str(&request.repo),
        )?;
        let grants = self.ctx.retain_unshielded(&caller, grants, |g| {
            (!g.namespace.is_empty()).then(|| g.namespace.clone())
        })?;
        let grants = self.members_hidden(&caller, grants, |g| g.principal_id.clone(), true)?;
        Ok(Response::new(ListGrantsResponse { grants }))
    }

    /// **Who am I?** The caller's own principal, the key that signed this
    /// call, and the highest server-wide level it holds — asked of the same
    /// decision every verb makes ([`crate::service::ServiceContext::check`]),
    /// so a locked-out account holds none. No grant is needed and no
    /// principal can be named: it answers about the caller and nobody else.
    async fn who_am_i(
        &self,
        request: Request<WhoAmIRequest>,
    ) -> Result<Response<WhoAmIResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        let principal = self
            .ctx
            .control
            .principal(&caller.principal_id)?
            .unwrap_or_else(|| gunnar_proto::Principal {
                id: caller.principal_id.clone(),
                name: caller.principal_id.clone(),
                ..Default::default()
            });
        let mut server_level = GrantLevel::Unspecified;
        for level in [GrantLevel::Admin, GrantLevel::Write, GrantLevel::Read] {
            if self.ctx.check(&caller, Scope::Server, level)?.is_none() {
                server_level = level;
                break;
            }
        }
        // **The account it acts in, and what it is to it** (2026-10-07): a
        // console draws the buying doors for an individual and an account
        // admin only, and a member's storage from the ACCOUNT's room.
        let standing = self.ctx.standing(&caller.principal_id)?;
        Ok(Response::new(WhoAmIResponse {
            principal: Some(principal),
            fingerprint: caller.fingerprint.clone(),
            server_level: server_level as i32,
            account: standing.account,
            account_role: standing.role.to_owned(),
            account_admin: standing.admin,
        }))
    }

    /// **May I?** The decision the verbs make, for the caller, without acting
    /// and without an audit record (see
    /// [`crate::service::ServiceContext::check`]). `Check{namespace: N, level:
    /// ADMIN}` is exactly the account-admin rule.
    async fn check(
        &self,
        request: Request<CheckRequest>,
    ) -> Result<Response<CheckResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        let request = request.into_inner();
        let level = GrantLevel::try_from(request.level)
            .ok()
            .filter(|level| *level != GrantLevel::Unspecified)
            .ok_or_else(|| Status::invalid_argument("`level`: READ, WRITE or ADMIN"))?;
        let namespace = request.namespace.trim();
        let repo = request.repo.trim();
        let access = match (namespace.is_empty(), repo.is_empty()) {
            (true, true) => Scope::Server,
            (true, false) => {
                return Err(Status::invalid_argument(
                    "`repo` needs its `namespace`: name both, or neither for the server",
                ))
            }
            (false, true) => Scope::namespace(namespace),
            (false, false) => Scope::Repo(gunnar_proto::RepoPath::new(namespace, repo)),
        };
        let refused = self.ctx.check(&caller, access, level)?;
        Ok(Response::new(CheckResponse {
            allowed: refused.is_none(),
            reason: refused.unwrap_or_default(),
        }))
    }

    /// **A passkey on a person** (Stage L). A server admin, or the person
    /// themselves (the self-scope — the call is already their own signed
    /// word). The key must be a P-256 point and the id within WebAuthn's
    /// bound; only a USER may hold one (the plane refuses anything else).
    async fn add_credential(
        &self,
        request: Request<AddCredentialRequest>,
    ) -> Result<Response<AddCredentialResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        if self.ctx.is_self(&caller, &request.get_ref().principal_id) {
            self.ctx
                .require_self(&caller, GrantLevel::Admin, "Access.AddCredential")?;
            self.refuse_a_mint_past_the_idp(&caller, "Access.AddCredential")?;
        } else {
            self.ctx.require(
                &caller,
                Scope::Server,
                GrantLevel::Admin,
                "Access.AddCredential",
            )?;
        }
        let request = request.into_inner();
        if request.principal_id.trim().is_empty() {
            return Err(Status::invalid_argument("`principal_id` is required"));
        }
        crate::webauthn::check_credential_id(&request.credential_id)
            .map_err(Status::invalid_argument)?;
        crate::webauthn::check_public_key(&request.public_key).map_err(Status::invalid_argument)?;
        let credential = self.ctx.control.add_credential(
            &request.principal_id,
            &request.credential_id,
            &request.public_key,
            request.label.trim(),
            request.sign_count,
        )?;
        self.ctx.account_event(
            self.ctx.account_of_principal(&credential.principal_id),
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "credential.added",
                credential.principal_id.clone(),
            )
            .detail(format!("passkey {:?}", credential.label)),
        );
        tracing::info!(
            target: crate::auth::AUDIT_TARGET,
            event = "access.credential_added",
            principal = %credential.principal_id,
            caller = %caller.principal_id,
            "a web credential was registered"
        );
        Ok(Response::new(AddCredentialResponse {
            credential: Some(credential),
        }))
    }

    /// **The caller's encryption key** (CRYPT-YUBI): the caller's own, always — there is
    /// no principal to name, so nobody reads another person's methods, an admin neither.
    async fn get_encryption_key(
        &self,
        request: Request<GetEncryptionKeyRequest>,
    ) -> Result<Response<GetEncryptionKeyResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        self.ctx
            .require_self(&caller, GrantLevel::Read, "Access.GetEncryptionKey")?;
        Ok(Response::new(
            self.ctx.control.encryption_key(&caller.principal_id)?,
        ))
    }

    /// The caller's first encryption key: its public half and its first method.
    async fn create_encryption_key(
        &self,
        request: Request<CreateEncryptionKeyRequest>,
    ) -> Result<Response<CreateEncryptionKeyResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        self.ctx
            .require_self(&caller, GrantLevel::Admin, "Access.CreateEncryptionKey")?;
        let request = request.into_inner();
        let first = request.first.ok_or_else(|| {
            Status::invalid_argument("`first` (the first unlock method) is required")
        })?;
        let kind = first.kind.clone();
        let key = self.ctx.control.create_encryption_key(
            &caller.principal_id,
            &request.public_key,
            first,
        )?;
        self.ctx.account_event(
            self.ctx.account_of_principal(&caller.principal_id),
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "encryption-key.created",
                caller.principal_id.clone(),
            )
            .detail(format!("first method {kind}")),
        );
        tracing::info!(target: crate::auth::AUDIT_TARGET, event = "access.encryption_key_created", principal = %caller.principal_id, method = %kind, "an encryption key was created");
        Ok(Response::new(CreateEncryptionKeyResponse {
            public_key: key.public_key,
            methods: key.methods,
        }))
    }

    /// One more unlock method of the caller's key.
    async fn add_unlock_method(
        &self,
        request: Request<AddUnlockMethodRequest>,
    ) -> Result<Response<AddUnlockMethodResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        self.ctx
            .require_self(&caller, GrantLevel::Admin, "Access.AddUnlockMethod")?;
        let method = request
            .into_inner()
            .method
            .ok_or_else(|| Status::invalid_argument("`method` is required"))?;
        let kind = method.kind.clone();
        let key = self
            .ctx
            .control
            .add_unlock_method(&caller.principal_id, method)?;
        self.ctx.account_event(
            self.ctx.account_of_principal(&caller.principal_id),
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "encryption-key.method-added",
                caller.principal_id.clone(),
            )
            .detail(kind.clone()),
        );
        tracing::info!(target: crate::auth::AUDIT_TARGET, event = "access.unlock_method_added", principal = %caller.principal_id, method = %kind, "an unlock method was added");
        Ok(Response::new(AddUnlockMethodResponse {
            methods: key.methods,
        }))
    }

    /// Delete one of the caller's unlock methods; never the last.
    async fn remove_unlock_method(
        &self,
        request: Request<RemoveUnlockMethodRequest>,
    ) -> Result<Response<RemoveUnlockMethodResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        self.ctx
            .require_self(&caller, GrantLevel::Admin, "Access.RemoveUnlockMethod")?;
        let id = request.into_inner().id;
        let key = self
            .ctx
            .control
            .remove_unlock_method(&caller.principal_id, id.trim())?;
        self.ctx.account_event(
            self.ctx.account_of_principal(&caller.principal_id),
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "encryption-key.method-removed",
                caller.principal_id.clone(),
            )
            .detail(id.clone()),
        );
        tracing::info!(target: crate::auth::AUDIT_TARGET, event = "access.unlock_method_removed", principal = %caller.principal_id, method_id = %id, "an unlock method was removed");
        Ok(Response::new(RemoveUnlockMethodResponse {
            methods: key.methods,
        }))
    }

    /// A person's own passkeys (the self-scope), or anybody's — every one, for
    /// an empty `principal_id` — for a server admin.
    async fn list_credentials(
        &self,
        request: Request<ListCredentialsRequest>,
    ) -> Result<Response<ListCredentialsResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        if self.ctx.is_self(&caller, &request.get_ref().principal_id) {
            self.ctx
                .require_self(&caller, GrantLevel::Read, "Access.ListCredentials")?;
        } else {
            self.ctx.require(
                &caller,
                Scope::Server,
                GrantLevel::Admin,
                "Access.ListCredentials",
            )?;
        }
        let request = request.into_inner();
        let credentials = self
            .ctx
            .control
            .credentials(opt_str(&request.principal_id))?;
        // **A passkey inside an account closed to the caller is shown without
        // its holder or its label** (2026-10-05): its id is an opaque random
        // handle the console's relay offers a browser at login, and nothing
        // else on the row is the operator's business.
        let shown = self.members_hidden(
            &caller,
            credentials.clone(),
            |c| c.principal_id.clone(),
            true,
        )?;
        let credentials = if shown.len() == credentials.len() {
            shown
        } else {
            let kept: std::collections::BTreeSet<Vec<u8>> =
                shown.iter().map(|c| c.credential_id.clone()).collect();
            credentials
                .into_iter()
                .map(|mut c| {
                    if !kept.contains(&c.credential_id) {
                        c.principal_id = String::new();
                        c.label = String::new();
                    }
                    c
                })
                .collect()
        };
        Ok(Response::new(ListCredentialsResponse { credentials }))
    }

    /// **Revoke a passkey.** The person's own (the self-scope); anybody's for
    /// a server admin. Somebody else's and one that does not exist are refused
    /// in the same words, so the refusal is no oracle for whose a credential
    /// is.
    async fn revoke_credential(
        &self,
        request: Request<RevokeCredentialRequest>,
    ) -> Result<Response<RevokeCredentialResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        let request = request.into_inner();
        crate::webauthn::check_credential_id(&request.credential_id)
            .map_err(Status::invalid_argument)?;
        let own = match self.ctx.control.credential(&request.credential_id)? {
            Some(stored) => {
                caller.authenticated && stored.credential.principal_id == caller.principal_id
            }
            None => false,
        };
        if own && self.ctx.is_self(&caller, &caller.principal_id) {
            self.ctx
                .require_self(&caller, GrantLevel::Admin, "Access.RevokeCredential")?;
        } else {
            self.ctx.require(
                &caller,
                Scope::Server,
                GrantLevel::Admin,
                "Access.RevokeCredential",
            )?;
        }
        self.refuse_the_last_admin_passkey(&request.credential_id)?;
        let Some(revoked) = self.ctx.control.revoke_credential(&request.credential_id)? else {
            return Err(Status::not_found("no such web credential"));
        };
        // **The sessions it opened end with it** (IDENTITY-HOST): every core
        // session of its person; they log in again with a passkey they hold.
        if let Some(identity) = &self.ctx.identity {
            if let Err(why) = identity.end_sessions(&revoked.principal_id, &caller.principal_id) {
                tracing::error!(target: crate::auth::AUDIT_TARGET, event = "access.sessions_not_ended", principal = %revoked.principal_id, reason = %why, "a passkey was revoked and its person's sessions could not be ended");
            }
        }
        self.ctx.account_event(
            self.ctx.account_of_principal(&revoked.principal_id),
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "credential.removed",
                revoked.principal_id.clone(),
            )
            .detail(format!(
                "passkey {:?} and the sessions it opened",
                revoked.label
            )),
        );
        Ok(Response::new(RevokeCredentialResponse {}))
    }

    /// **Change a passkey's label**: its own principal (the self-scope), or a
    /// server admin. Somebody else's and a missing one are refused in the
    /// same words.
    async fn rename_credential(
        &self,
        request: Request<RenameCredentialRequest>,
    ) -> Result<Response<RenameCredentialResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        let request = request.into_inner();
        crate::webauthn::check_credential_id(&request.credential_id)
            .map_err(Status::invalid_argument)?;
        let label = request.label.trim();
        if label.is_empty() || label.chars().count() > 128 {
            return Err(Status::invalid_argument(
                "`label`: not empty, at most 128 characters",
            ));
        }
        let own = match self.ctx.control.credential(&request.credential_id)? {
            Some(stored) => {
                caller.authenticated && stored.credential.principal_id == caller.principal_id
            }
            None => false,
        };
        if own && self.ctx.is_self(&caller, &caller.principal_id) {
            self.ctx
                .require_self(&caller, GrantLevel::Admin, "Access.RenameCredential")?;
        } else {
            self.ctx.require(
                &caller,
                Scope::Server,
                GrantLevel::Admin,
                "Access.RenameCredential",
            )?;
        }
        let credential = self
            .ctx
            .control
            .rename_credential(&request.credential_id, label)?
            .ok_or_else(|| Status::not_found("no such web credential"))?;
        self.ctx.account_event(
            self.ctx.account_of_principal(&credential.principal_id),
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "credential.renamed",
                credential.principal_id.clone(),
            )
            .detail(format!("passkey now {:?}", credential.label)),
        );
        Ok(Response::new(RenameCredentialResponse {
            credential: Some(credential),
        }))
    }

    /// **A machine asks for a web credential** (2026-10-05) — relayed by the
    /// console (a server admin), anonymously on the machine's behalf. The
    /// creation ceremony is checked over a `MachineChallenge` (the challenge
    /// spent whatever the outcome); the request is then HELD, pending, and nothing
    /// else happens until a person approves it.
    async fn request_machine_enrolment(
        &self,
        request: Request<RequestMachineEnrolmentRequest>,
    ) -> Result<Response<RequestMachineEnrolmentResponse>, Status> {
        const METHOD: &str = "Access.RequestMachineEnrolment";
        let caller = self
            .ctx
            .authorize(&request, Scope::Server, GrantLevel::Admin, METHOD)?;
        let request = request.into_inner();
        let machine = request.machine.trim();
        crate::machine_enrolment::check_machine_name(machine).map_err(Status::invalid_argument)?;
        let level = machine_level(request.server_level)?;
        let label = request.label.trim();
        if label.is_empty() || label.chars().count() > 128 {
            return Err(Status::invalid_argument(
                "`label`: what the machine calls itself, not empty, at most 128 characters",
            ));
        }
        crate::webauthn::check_credential_id(&request.credential_id)
            .map_err(Status::invalid_argument)?;
        crate::webauthn::check_public_key(&request.public_key).map_err(Status::invalid_argument)?;
        if request.rp_id.trim().is_empty() || request.origin.trim().is_empty() {
            return Err(Status::invalid_argument(
                "`rp_id` and `origin` are both required",
            ));
        }
        let now = crate::bus::now_unix_ms();
        let data = crate::webauthn::client_data(&request.client_data_json)
            .map_err(|why| ceremony_refused(METHOD, why))?;
        let fresh = self.ctx.web_challenges.spend(&data.challenge, now);
        crate::webauthn::check_ceremony(&data, "webauthn.create", &request.origin, &request.rp_id)
            .and_then(|()| {
                crate::webauthn::check_relying_party(
                    &self.ctx.config.web_relying_parties,
                    &request.rp_id,
                    &request.origin,
                )
            })
            .map_err(|why| ceremony_refused(METHOD, why))?;
        if !fresh {
            return Err(ceremony_refused(
                METHOD,
                "the challenge was not issued here, was already used, or expired".to_owned(),
            ));
        }
        // Stinky row 13 (2026-10-10): the key is the one the authenticator
        // attested, not the relay's word.
        let attested = crate::webauthn::verify_attestation(
            &request.attestation_object,
            &request.client_data_json,
            &request.rp_id,
            &request.credential_id,
            &request.public_key,
        )
        .map_err(|why| ceremony_refused(METHOD, why))?;
        if self
            .ctx
            .control
            .credential(&request.credential_id)?
            .is_some()
        {
            return Err(Status::already_exists(
                "this credential is registered at gunnar already",
            ));
        }
        let (row, expired) = self.ctx.machine_enrolments.hold(
            machine,
            label,
            level,
            &request.credential_id,
            &request.public_key,
            attested.sign_count,
            now,
        )?;
        audit_expired(expired);
        tracing::info!(
            target: crate::auth::AUDIT_TARGET,
            event = "access.machine_enrolment_requested",
            request = %row.request_id,
            machine = %row.machine,
            label = %row.label,
            role = crate::auth::level_name(level),
            code = %row.code,
            key = %row.key_fingerprint,
            relay = %caller.principal_id,
            "a machine asked for a web credential; it waits for an admin's approval"
        );
        Ok(Response::new(RequestMachineEnrolmentResponse {
            request: Some(row),
        }))
    }

    /// One request's state — the relay's, for the machine's poll.
    async fn machine_enrolment_status(
        &self,
        request: Request<MachineEnrolmentStatusRequest>,
    ) -> Result<Response<MachineEnrolmentStatusResponse>, Status> {
        self.ctx.authorize(
            &request,
            Scope::Server,
            GrantLevel::Admin,
            "Access.MachineEnrolmentStatus",
        )?;
        let id = request.into_inner().request_id.trim().to_owned();
        let (held, expired) = self
            .ctx
            .machine_enrolments
            .get(&id, crate::bus::now_unix_ms());
        audit_expired(expired);
        let held = held.ok_or_else(|| {
            Status::not_found(format!(
                "no machine enrolment request {id}: it was never made here, or it is long gone"
            ))
        })?;
        Ok(Response::new(MachineEnrolmentStatusResponse {
            request: Some(held.row),
        }))
    }

    /// The pending requests: a server admin's.
    async fn list_machine_enrolments(
        &self,
        request: Request<ListMachineEnrolmentsRequest>,
    ) -> Result<Response<ListMachineEnrolmentsResponse>, Status> {
        self.ctx.authorize(
            &request,
            Scope::Server,
            GrantLevel::Admin,
            "Access.ListMachineEnrolments",
        )?;
        let (requests, expired) = self
            .ctx
            .machine_enrolments
            .pending(crate::bus::now_unix_ms());
        audit_expired(expired);
        Ok(Response::new(ListMachineEnrolmentsResponse { requests }))
    }

    /// ★ **Approve a machine's request** — the ONE door to a machine's web
    /// credential, an admin one included. A server admin who is a PERSON, with
    /// a fresh user-verified assertion of their OWN passkey over a challenge
    /// gunnar issued (the step-up, verified here as a login is). Nothing else:
    /// the installer's break-glass token is the owner's FIRST INVITE (he
    /// registers with it first), never a machine's approval. Then the machine
    /// principal, its grant and its credential, in one update.
    async fn approve_machine_enrolment(
        &self,
        request: Request<ApproveMachineEnrolmentRequest>,
    ) -> Result<Response<ApproveMachineEnrolmentResponse>, Status> {
        const METHOD: &str = "Access.ApproveMachineEnrolment";
        let caller = self
            .ctx
            .authorize(&request, Scope::Server, GrantLevel::Admin, METHOD)?;
        let stepped_up = crate::auth::stepped_up(&request);
        let request = request.into_inner();
        let id = request.request_id.trim().to_owned();
        let now = crate::bus::now_unix_ms();
        let (held, expired) = self.ctx.machine_enrolments.get(&id, now);
        audit_expired(expired);
        let held = held.ok_or_else(|| {
            Status::not_found(format!(
                "no machine enrolment request {id}: it was never made here, or it is long gone"
            ))
        })?;
        match gunnar_proto::MachineEnrolmentState::try_from(held.row.state) {
            Ok(gunnar_proto::MachineEnrolmentState::Pending) => {}
            Ok(gunnar_proto::MachineEnrolmentState::Expired) => {
                return Err(Status::failed_precondition(
                    "this request expired with nobody's approval; the machine asks again",
                ))
            }
            _ => {
                return Err(Status::failed_precondition(format!(
                    "this request was {} already, by {}",
                    crate::machine_enrolment::state_word(held.row.state),
                    held.row.decided_by
                )))
            }
        }
        if request.code.trim() != held.row.code {
            return Err(Status::failed_precondition(format!(
                "the code {:?} is not this request's: approve the request whose code the machine \
                 shows",
                request.code.trim()
            )));
        }
        let (by, door, step_up) = {
            // The human is the point: a machine that holds Admin — an
            // approved kit — never approves another machine.
            let approver = self.ctx.control.principal(&caller.principal_id)?;
            if approver.as_ref().map(|p| p.kind) != Some(gunnar_proto::PrincipalKind::User as i32) {
                return Err(Status::permission_denied(format!(
                    "only a person approves a machine's credential, and {} is not a person",
                    caller.principal_id
                )));
            }
            // The core's step-up (IDENTITY-HOST): the approver's own session,
            // stepped up by a fresh user-verified touch within the window. A
            // signed key and the break-glass token are never a step-up.
            if !stepped_up {
                tracing::warn!(target: crate::auth::AUDIT_TARGET, event = "access.machine_enrolment_step_up_refused", request = %id, by = %caller.principal_id, "an approval came from no stepped-up session");
                return Err(Status::permission_denied(format!(
                    "[stepup] {METHOD} needs a fresh touch of your own passkey (user-verified, within the last 10 minutes)"
                )));
            }
            (caller.principal_id.clone(), "step-up", None)
        };
        // Claimed BEFORE the commit, so a refusal racing it finds it decided;
        // put back if the plane says no.
        let decided = self
            .ctx
            .machine_enrolments
            .decide(&id, gunnar_proto::MachineEnrolmentState::Approved, &by, now)
            .ok_or_else(|| Status::failed_precondition("this request was decided meanwhile"))?;
        // The approver GIVES the role: the one asked for, unless they name
        // another (owner order 2026-10-18).
        let level = if request.server_level == GrantLevel::Unspecified as i32 {
            machine_level(held.row.server_level)?
        } else {
            machine_level(request.server_level)?
        };
        let committed = self
            .ctx
            .control
            .approve_machine_enrolment(crate::seam::MachineApproval {
                machine: held.row.machine.clone(),
                server_level: level,
                credential_id: held.row.credential_id.clone(),
                public_key: held.public_key.clone(),
                label: held.row.label.clone(),
                sign_count: held.sign_count,
                now_unix_ms: now,
                step_up,
            });
        let approved = match committed {
            Ok(approved) => approved,
            Err(err) => {
                self.ctx.machine_enrolments.reopen(&id);
                tracing::warn!(
                    target: crate::auth::AUDIT_TARGET,
                    event = "access.machine_enrolment_not_approved",
                    request = %id,
                    machine = %held.row.machine,
                    by = %by,
                    door,
                    reason = %err,
                    "an approval of a machine enrolment was refused by the identity table"
                );
                return Err(err.into());
            }
        };
        tracing::info!(
            target: crate::auth::AUDIT_TARGET,
            event = "access.machine_enrolment_approved",
            request = %decided.request_id,
            machine = %decided.machine,
            role = crate::auth::level_name(level),
            code = %decided.code,
            key = %decided.key_fingerprint,
            by = %by,
            door,
            created = approved.created,
            "a machine's web credential was approved and enrolled on its machine principal"
        );
        // The approver's own account log (2026-10-05, approvals): who let
        // which machine in, as what, under which code.
        self.ctx.account_event(
            self.ctx.account_of_principal(&caller.principal_id),
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "approval.machine",
                decided.machine.clone(),
            )
            .detail(format!(
                "{} as {} — code {} — {}",
                decided.label,
                crate::auth::level_name(level),
                decided.code,
                door
            )),
        );
        Ok(Response::new(ApproveMachineEnrolmentResponse {
            request: Some(decided),
            principal: Some(approved.principal),
            credential: Some(approved.credential),
        }))
    }

    /// Refuse a machine's request: a server admin. Nothing is stored.
    async fn refuse_machine_enrolment(
        &self,
        request: Request<RefuseMachineEnrolmentRequest>,
    ) -> Result<Response<RefuseMachineEnrolmentResponse>, Status> {
        let caller = self.ctx.authorize(
            &request,
            Scope::Server,
            GrantLevel::Admin,
            "Access.RefuseMachineEnrolment",
        )?;
        let id = request.into_inner().request_id.trim().to_owned();
        let now = crate::bus::now_unix_ms();
        let Some(decided) = self.ctx.machine_enrolments.decide(
            &id,
            gunnar_proto::MachineEnrolmentState::Refused,
            &caller.principal_id,
            now,
        ) else {
            let (held, expired) = self.ctx.machine_enrolments.get(&id, now);
            audit_expired(expired);
            return Err(match held {
                Some(held) => Status::failed_precondition(format!(
                    "this request is {} already",
                    crate::machine_enrolment::state_word(held.row.state)
                )),
                None => Status::not_found(format!("no machine enrolment request {id}")),
            });
        };
        tracing::info!(
            target: crate::auth::AUDIT_TARGET,
            event = "access.machine_enrolment_refused",
            request = %decided.request_id,
            machine = %decided.machine,
            code = %decided.code,
            by = %caller.principal_id,
            "a machine enrolment request was refused; nothing was stored"
        );
        self.ctx.account_event(
            self.ctx.account_of_principal(&caller.principal_id),
            crate::enterprise::AccountAuditEvent::by(
                &caller,
                "approval.machine_refused",
                decided.machine.clone(),
            )
            .detail(format!("{} — code {}", decided.label, decided.code)),
        );
        Ok(Response::new(RefuseMachineEnrolmentResponse {
            request: Some(decided),
        }))
    }

    async fn add_group_member(
        &self,
        request: Request<AddGroupMemberRequest>,
    ) -> Result<Response<AddGroupMemberResponse>, Status> {
        // Adding a member IS handing that member every grant the group holds.
        // Server Admin, the same gate as AddPrincipal, AddKey and a
        // server-scoped Grant. A non-admin — a member trying to add themselves
        // to a powerful group — is refused here, before the control plane.
        let caller = self.ctx.caller(&request)?;
        self.ctx.require(
            &caller,
            Scope::Server,
            GrantLevel::Admin,
            "Access.AddGroupMember",
        )?;
        let request = request.into_inner();
        if request.group_id.is_empty() || request.member_id.is_empty() {
            return Err(Status::invalid_argument(
                "`group_id` and `member_id` are both required",
            ));
        }
        let signed = check_membership_signature(
            &caller,
            request.signed,
            &request.group_id,
            &request.member_id,
            MembershipAction::Add,
        )?;
        let index =
            self.ctx
                .control
                .add_group_member(&request.group_id, &request.member_id, signed)?;
        Ok(Response::new(AddGroupMemberResponse { index }))
    }

    async fn remove_group_member(
        &self,
        request: Request<RemoveGroupMemberRequest>,
    ) -> Result<Response<RemoveGroupMemberResponse>, Status> {
        let caller = self.ctx.caller(&request)?;
        self.ctx.require(
            &caller,
            Scope::Server,
            GrantLevel::Admin,
            "Access.RemoveGroupMember",
        )?;
        let request = request.into_inner();
        if request.group_id.is_empty() || request.member_id.is_empty() {
            return Err(Status::invalid_argument(
                "`group_id` and `member_id` are both required",
            ));
        }
        let signed = check_membership_signature(
            &caller,
            request.signed,
            &request.group_id,
            &request.member_id,
            MembershipAction::Remove,
        )?;
        let (was_member, index) =
            self.ctx
                .control
                .remove_group_member(&request.group_id, &request.member_id, signed)?;
        Ok(Response::new(RemoveGroupMemberResponse {
            was_member,
            index,
        }))
    }

    async fn list_group_members(
        &self,
        request: Request<ListGroupMembersRequest>,
    ) -> Result<Response<ListGroupMembersResponse>, Status> {
        // A membership listing is the reconnaissance half of the same attack a
        // principal or key listing is, so it takes Admin too.
        self.ctx.authorize(
            &request,
            Scope::Server,
            GrantLevel::Admin,
            "Access.ListGroupMembers",
        )?;
        let request = request.into_inner();
        if request.group_id.is_empty() {
            return Err(Status::invalid_argument("`group_id` is required"));
        }
        let members = self.ctx.control.list_group_members(&request.group_id)?;
        Ok(Response::new(ListGroupMembersResponse { members }))
    }

    async fn list_group_membership_events(
        &self,
        request: Request<ListGroupMembershipEventsRequest>,
    ) -> Result<Response<ListGroupMembershipEventsResponse>, Status> {
        // Reading the audit trail of who was ever in a group is the same
        // reconnaissance a live membership listing is, so it takes Admin too.
        self.ctx.authorize(
            &request,
            Scope::Server,
            GrantLevel::Admin,
            "Access.ListGroupMembershipEvents",
        )?;
        let request = request.into_inner();
        let filter = opt_str(&request.group_id);
        let events = self.ctx.control.membership_events(filter)?;
        Ok(Response::new(ListGroupMembershipEventsResponse { events }))
    }
}

/// **The membership event must be the caller's own word about this exact
/// change.** The signature proves which key spoke and the seam checks it
/// against the ACL key table; this checks, before anything is applied, that the
/// event describes the change the request names and is attributed to the
/// principal on the connection. Without it an admin could sign `remove alice`
/// and apply it as `add mallory`, or file the change in a colleague's name —
/// and the log would carry a signature that says something the server did not
/// do.
fn check_membership_signature(
    caller: &Caller,
    signed: Option<SignedMembershipEvent>,
    group_id: &str,
    member_id: &str,
    action: MembershipAction,
) -> Result<SignedMembershipEvent, Status> {
    let signed = signed.ok_or_else(|| {
        Status::invalid_argument(
            "`signed` is required: a membership change is the acting admin's signed word, not \
             the server's, so it must carry the admin's own sshsig",
        )
    })?;
    let event = signed
        .event
        .as_ref()
        .ok_or_else(|| Status::invalid_argument("`signed.event` is required"))?;
    if event.group != group_id || event.member != member_id {
        return Err(Status::invalid_argument(format!(
            "the signed event is about {:?}/{:?} but the request names {:?}/{:?}; a signature \
             for one membership may not be applied to another",
            event.group, event.member, group_id, member_id
        )));
    }
    if event.action != action as i32 {
        return Err(Status::invalid_argument(
            "the signed event's action does not match the RPC (add vs remove); the action is \
             inside the signature and may not be repurposed",
        ));
    }
    if !caller.principal_id.is_empty() && event.actor != caller.principal_id {
        return Err(Status::permission_denied(format!(
            "this membership change is attributed to {:?} and the caller is {:?}; a membership \
             change is evidence about who acted, so it may not be filed in another principal's \
             name",
            event.actor, caller.principal_id
        )));
    }
    Ok(signed)
}

/// The scope a grant is administered at: the repository when the grant names
/// one, otherwise the namespace. One writer, used by both `Grant` and
/// `Revoke`, so the two cannot disagree about who may edit what.
fn grant_scope(namespace: &str, repo: &str) -> Scope {
    if repo.is_empty() {
        Scope::namespace(namespace)
    } else {
        Scope::Repo(gunnar_proto::RepoPath::new(namespace, repo))
    }
}