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/tests/account_model.rs

//! **The owner's account model (2026-10-03), on the control plane.**
//!
//! "a user starts an account. he can choose to enroll new users=employees then
//! it becomes an enterprise account meaning its members (except admin=first
//! person) cant make repos public. default for all accounts is that emitted
//! events and namespaces are not public, such things has to be set. … what
//! about users that has an account can they become employee .. no". Nothing is
//! bought; there is no Enterprise flag.
//!
//! 1. Every account, by default: its namespace is NOT public and its events
//!    are NOT shown to anyone outside the account. Making either public is an
//!    explicit setting (`Repos.SetNamespaceSettings`).
//! 2. Only the ACCOUNT admin changes a repository's visibility or those
//!    settings — whoever holds `Admin` over the NAMESPACE: the principal it is
//!    named after (intrinsically, `AuthTable::owns_account`), a namespace
//!    `Admin` grant, or a server-scoped one. An `Admin` grant on one
//!    repository is not that.
//! 3. An enterprise account is one whose admin has ENROLLED employees
//!    (`Accounts.EnrollEmployee`): NEW principals bound to the account, never an
//!    existing one. A member may not make anything public (rule 2).
//! 4. To an outsider, a private namespace shows only its public repositories,
//!    and one holding none answers as a namespace that does not exist.
//!
//! Driven with authentication ON (the shipped default) and the caller attached
//! as the gate would attach it; `tests/auth.rs` is where the gate itself is
//! proven.

use std::sync::Arc;
use std::time::Duration;

use gunnar_grpc::{
    AccountsService, Caller, ControlPlane, EventBus, EventsService, MemoryControlPlane, NewRepo,
    ReposService, ServerConfig, ServiceContext,
};
use gunnar_proto::accounts_server::Accounts as _;
use gunnar_proto::events_server::Events as _;
use gunnar_proto::repos_server::Repos as _;
use gunnar_proto::*;
use tokio_stream::StreamExt as _;
use tonic::{Code, Request};

/// The account: a namespace named after the principal that owns it.
const ACCOUNT: &str = "acme";
/// Private.
const REPO: &str = "app";
/// Public, once [`Rig::with_a_public_repo`] made it.
const OPEN: &str = "open";

struct Rig {
    plane: Arc<MemoryControlPlane>,
    /// The identity core (IDENTITY-HOST): an enrolment's first-login link is its.
    identity: Arc<gunnar_grpc::identity::GunnarIdentity>,
    bus: Arc<EventBus>,
    repos: ReposService,
    accounts: AccountsService,
    events: EventsService,
}

fn with<T>(caller: &Caller, body: T) -> Request<T> {
    let mut request = Request::new(body);
    request.extensions_mut().insert(caller.clone());
    request
}

impl Rig {
    /// The core's open passkey links: (the person, who minted it).
    fn invites(&self) -> Vec<(String, String)> {
        use nornir_identity::IdentityHost as _;
        let now = self.identity.host().now_ms() / 1000;
        self.identity
            .host()
            .gunnar_store()
            .tables()
            .invites
            .into_iter()
            .filter(|i| i.open(now))
            .filter_map(|i| {
                let rest = i
                    .created_by
                    .strip_prefix(nornir_identity::PERSON_INVITE)?
                    .to_owned();
                let (person, by) = rest.split_once(':')?;
                Some((person.to_owned(), by.to_owned()))
            })
            .collect()
    }

    /// `acme/app`, private, with the account's owner `acme` and nobody else.
    fn new() -> Rig {
        let plane = Arc::new(MemoryControlPlane::with_repo(ACCOUNT, REPO));
        plane
            .add_principal(ACCOUNT, ACCOUNT, PrincipalKind::User)
            .expect("the account's owner");
        let control: Arc<dyn ControlPlane> = plane.clone();
        let bus = Arc::new(EventBus::new(64));
        let store = gunnar_grpc::identity::GunnarStore::new(
            Arc::clone(&control),
            Default::default(),
            None,
            gunnar_grpc::identity::system_clock(),
        );
        let identity = Arc::new(nornir_identity::Identity::new(
            gunnar_grpc::identity::GunnarHost::new(
                nornir_identity::Site::Private,
                store,
                Arc::new(nornir_identity::NoMail),
                gunnar_grpc::identity::system_clock(),
            ),
            nornir_identity::Config::default(),
        ));
        let ctx = ServiceContext::new(control, bus.clone(), ServerConfig::default())
            .with_identity(Arc::clone(&identity));
        Rig {
            identity,
            repos: ReposService::new(ctx.clone()),
            accounts: AccountsService::new(ctx.clone()),
            events: EventsService::new(ctx),
            bus,
            plane,
        }
    }

    fn repo(&self, namespace: &str, name: &str, visibility: Visibility) {
        if self.plane.namespace(namespace).expect("ask").is_none() {
            self.plane
                .create_namespace(namespace, "")
                .expect("namespace");
        }
        self.plane
            .create_repo(NewRepo {
                path: RepoPath::new(namespace, name),
                hash_kind: HashKind::Sha256,
                default_branch: String::new(),
                visibility,
                description: String::new(),
                adopt: false,
            })
            .expect("repo");
    }

    fn with_a_public_repo(self) -> Rig {
        self.repo(ACCOUNT, OPEN, Visibility::Public);
        self
    }

    fn principal(&self, id: &str) -> Caller {
        self.plane
            .add_principal(id, id, PrincipalKind::User)
            .expect("principal");
        Rig::caller(id)
    }

    fn caller(id: &str) -> Caller {
        Caller {
            principal_id: id.to_owned(),
            fingerprint: format!("SHA256:{id}"),
            machine: false,
            authenticated: true,
        }
    }

    fn owner() -> Caller {
        Rig::caller(ACCOUNT)
    }

    async fn set(&self, caller: &Caller, to: Visibility) -> Result<Repo, tonic::Status> {
        self.repos
            .set_visibility(with(
                caller,
                SetVisibilityRequest {
                    path: Some(RepoPath::new(ACCOUNT, REPO)),
                    visibility: to as i32,
                },
            ))
            .await
            .map(|response| response.into_inner().repo.expect("a repo comes back"))
    }

    async fn settings(
        &self,
        caller: &Caller,
        public: Option<bool>,
        events_public: Option<bool>,
    ) -> Result<Namespace, tonic::Status> {
        self.repos
            .set_namespace_settings(with(
                caller,
                SetNamespaceSettingsRequest {
                    name: ACCOUNT.into(),
                    public,
                    events_public,
                },
            ))
            .await
            .map(|r| r.into_inner().namespace.expect("the namespace comes back"))
    }

    async fn namespaces(&self, caller: &Caller) -> Result<Vec<Namespace>, tonic::Status> {
        self.repos
            .list_namespaces(with(caller, ListNamespacesRequest::default()))
            .await
            .map(|r| r.into_inner().namespaces)
    }

    async fn list(&self, caller: &Caller, namespace: &str) -> Result<Vec<String>, tonic::Status> {
        self.repos
            .list(with(
                caller,
                ListReposRequest {
                    namespace: namespace.into(),
                    ..Default::default()
                },
            ))
            .await
            .map(|r| {
                r.into_inner()
                    .repos
                    .into_iter()
                    .map(|repo| repo.path.expect("path").repo)
                    .collect()
            })
    }

    async fn enroll(&self, caller: &Caller, id: &str) -> Result<Principal, tonic::Status> {
        self.accounts
            .enroll_employee(with(
                caller,
                EnrollEmployeeRequest {
                    account: ACCOUNT.into(),
                    id: id.into(),
                    name: id.into(),
                    ..Default::default()
                },
            ))
            .await
            .map(|r| r.into_inner().principal.expect("the employee comes back"))
    }

    /// `Accounts.EnrollEmployee` as "Add users" calls it.
    async fn add(
        &self,
        caller: &Caller,
        ask: EnrollEmployeeRequest,
    ) -> Result<EnrollEmployeeResponse, tonic::Status> {
        self.accounts
            .enroll_employee(with(
                caller,
                EnrollEmployeeRequest {
                    account: ACCOUNT.into(),
                    ..ask
                },
            ))
            .await
            .map(|r| r.into_inner())
    }

    async fn employees(&self, caller: &Caller) -> Result<ListEmployeesResponse, tonic::Status> {
        self.accounts
            .list_employees(with(
                caller,
                ListEmployeesRequest {
                    account: ACCOUNT.into(),
                },
            ))
            .await
            .map(|r| r.into_inner())
    }

    /// The account holds `cap` bytes of room and stores `used` (the METERED
    /// appliance: a signed fact's `pack_bytes`, and gunnar's own measurement).
    #[cfg(feature = "metered")]
    fn room(&self, cap: u64, used: u64) {
        self.plane.set_entitlement_fact(EntitlementFact {
            namespace: ACCOUNT.to_owned(),
            plan: "room".to_owned(),
            state: EntitlementState::Paid as i32,
            paid_until_unix_ms: None,
            caps: [("pack_bytes".to_owned(), cap)].into_iter().collect(),
            source: "operator".to_owned(),
            ..Default::default()
        });
        self.plane.set_usage(NamespaceUsage {
            namespace: ACCOUNT.to_owned(),
            pack_bytes: used,
            ..Default::default()
        });
    }

    async fn watch(
        &self,
        caller: &Caller,
        namespaces: &[&str],
    ) -> Result<<EventsService as gunnar_proto::events_server::Events>::WatchStream, tonic::Status>
    {
        self.events
            .watch(with(
                caller,
                WatchRequest {
                    namespaces: namespaces.iter().map(|n| n.to_string()).collect(),
                    ..Default::default()
                },
            ))
            .await
            .map(|r| r.into_inner())
    }

    fn created(&self, name: &str) {
        self.created_in(ACCOUNT, name);
    }

    fn created_in(&self, namespace: &str, name: &str) {
        self.bus.publish(event::Payload::RepoCreated(RepoCreated {
            repo: Some(Repo {
                path: Some(RepoPath::new(namespace, name)),
                ..Default::default()
            }),
            principal_id: namespace.into(),
        }));
    }

    fn visibility(&self) -> i32 {
        self.plane
            .get_repo(&RepoPath::new(ACCOUNT, REPO))
            .expect("the repo")
            .visibility
    }
}

/// The next event on `stream` that is about a repository, within a second.
async fn next_repo_event<S>(stream: &mut S) -> Option<String>
where
    S: tokio_stream::Stream<Item = Result<Event, tonic::Status>> + Unpin,
{
    loop {
        match tokio::time::timeout(Duration::from_secs(1), stream.next()).await {
            Ok(Some(Ok(event))) => {
                if let Some(path) = event.repo_path() {
                    return Some(format!("{}/{}", path.namespace, path.repo));
                }
            }
            Ok(Some(Err(status))) => panic!("the stream refused: {status:?}"),
            Ok(None) | Err(_) => return None,
        }
    }
}

// ── rule 2: a repository's visibility is the ACCOUNT admin's ────────────────

/// ★ **A repo-level Admin who is not the account admin is refused**, with a
/// reason that says whose decision it is — and nothing was written.
#[tokio::test]
async fn a_repository_admin_who_is_not_the_account_admin_may_not_change_visibility() {
    let rig = Rig::new();
    let bob = rig.principal("bob");
    rig.plane
        .grant("bob", ACCOUNT, REPO, GrantLevel::Admin)
        .expect("Admin on the one repository");

    for to in [Visibility::Public, Visibility::Private] {
        let refused = rig
            .set(&bob, to)
            .await
            .expect_err("a repository Admin must not decide the account's visibility");
        assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
        let said = refused.message();
        assert!(
            said.contains("bob"),
            "the refusal names who it refused: {said}"
        );
        assert!(
            said.contains("account admin") && said.contains(ACCOUNT),
            "the refusal says it is the ACCOUNT admin's decision, and which account: {said}"
        );
    }
    assert_eq!(
        rig.visibility(),
        Visibility::Private as i32,
        "a refusal that had already written would be green above and wrong in the catalog"
    );
}

/// The account admin may — the owner (intrinsically) and a namespace-scoped
/// `Admin` grant alike, both directions — and a namespace `Write` may not.
#[tokio::test]
async fn the_account_admin_may_change_visibility() {
    let rig = Rig::new();
    let published = rig
        .set(&Rig::owner(), Visibility::Public)
        .await
        .expect("the owner publishes");
    assert_eq!(published.visibility, Visibility::Public as i32);
    assert_eq!(rig.visibility(), Visibility::Public as i32);

    let carol = rig.principal("carol");
    rig.plane
        .grant("carol", ACCOUNT, "", GrantLevel::Admin)
        .expect("Admin on the namespace");
    let retracted = rig
        .set(&carol, Visibility::Private)
        .await
        .expect("an account admin by grant retracts");
    assert_eq!(retracted.visibility, Visibility::Private as i32);

    let dave = rig.principal("dave");
    rig.plane
        .grant("dave", ACCOUNT, "", GrantLevel::Write)
        .expect("Write on the namespace");
    let refused = rig
        .set(&dave, Visibility::Public)
        .await
        .expect_err("write is not admin");
    assert_eq!(refused.code(), Code::PermissionDenied);
}

// ── rule 1: the namespace and its events are private until set ──────────────

/// ★ **A namespace is private by default**: an outsider listing namespaces does
/// not see it, nor may the outsider (or a repository admin) make it public.
/// Its account admin may, and then the outsider sees it.
#[tokio::test]
async fn a_namespace_is_private_to_outsiders_until_its_account_admin_makes_it_public() {
    let rig = Rig::new();
    let stranger = rig.principal("stranger");
    let bob = rig.principal("bob");
    rig.plane
        .grant("bob", ACCOUNT, REPO, GrantLevel::Admin)
        .expect("Admin on the one repository");

    let seen = rig
        .namespaces(&stranger)
        .await
        .expect("an outsider may list");
    assert!(
        seen.iter().all(|ns| ns.name != ACCOUNT),
        "a private namespace was listed to an outsider: {seen:?}"
    );
    let mine = rig
        .namespaces(&Rig::owner())
        .await
        .expect("the owner lists");
    let acme = mine
        .iter()
        .find(|ns| ns.name == ACCOUNT)
        .expect("its own account");
    assert!(!acme.public && !acme.events_public, "born public: {acme:?}");

    for who in [&stranger, &bob] {
        let refused = rig
            .settings(who, Some(true), None)
            .await
            .expect_err("only the account admin makes the namespace public");
        assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
        assert!(refused.message().contains("account admin"), "{refused:?}");
    }
    assert!(
        !rig.plane
            .namespace(ACCOUNT)
            .expect("ask")
            .expect("there")
            .public
    );

    let set = rig
        .settings(&Rig::owner(), Some(true), None)
        .await
        .expect("the account admin makes it public");
    assert!(set.public && !set.events_public, "{set:?}");
    let seen = rig
        .namespaces(&stranger)
        .await
        .expect("an outsider may list");
    assert!(
        seen.iter().any(|ns| ns.name == ACCOUNT && ns.public),
        "a public namespace is shown to everyone: {seen:?}"
    );
}

/// ★ **An account's events are shown to nobody outside it by default.** A
/// member sees them; an outsider is refused until the account admin makes them
/// public — and even then never sees an event about a PRIVATE repository.
#[tokio::test]
async fn an_accounts_events_are_shown_to_nobody_outside_it_until_its_admin_makes_them_public() {
    let rig = Rig::new().with_a_public_repo();
    let stranger = rig.principal("stranger");

    let refused = rig
        .watch(&stranger, &[ACCOUNT])
        .await
        .err()
        .expect("an outsider may not watch a private account's events");
    assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
    // Naming NO account is "every account I am inside of" (2026-10-04, the
    // thin layer: the console watches as the person). For a stranger that is
    // none: the watch is accepted, and the first repository event it is ever
    // handed is acme's PUBLIC repository, after its events were made public.
    let mut strangers_own = rig
        .watch(&stranger, &[])
        .await
        .expect("a stranger's own watch is accepted and is theirs alone");

    // A member — an employee — watches its own account.
    rig.enroll(&Rig::owner(), "bob").await.expect("enroll bob");
    let mut member = rig
        .watch(&Rig::caller("bob"), &[ACCOUNT])
        .await
        .expect("a member watches its account");
    rig.created(REPO);
    assert_eq!(
        next_repo_event(&mut member).await.as_deref(),
        Some("acme/app")
    );

    // The outsider may not flip it; the account admin may.
    let refused = rig
        .settings(&stranger, None, Some(true))
        .await
        .expect_err("only the account admin makes the events public");
    assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
    let set = rig
        .settings(&Rig::owner(), None, Some(true))
        .await
        .expect("the account admin makes the events public");
    assert!(set.events_public && !set.public, "{set:?}");

    let mut outsider = rig
        .watch(&stranger, &[ACCOUNT])
        .await
        .expect("public events are shown to an outsider");
    rig.created(REPO);
    rig.created(OPEN);
    assert_eq!(
        next_repo_event(&mut outsider).await.as_deref(),
        Some("acme/open"),
        "an outsider was shown an event about a private repository first, or nothing"
    );
    assert_eq!(
        next_repo_event(&mut strangers_own).await.as_deref(),
        Some("acme/open"),
        "a stranger's own watch carried a private account's event"
    );
    // Watching more than the public account is still refused.
    let refused = rig
        .watch(&stranger, &[ACCOUNT, "other"])
        .await
        .err()
        .expect("a second, private account in the same watch");
    assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
}

// ── the caller's OWN watch: naming no account (2026-10-04, the thin layer) ──

/// ★ **A watch naming no account carries every account the caller is INSIDE,
/// and no other.** The account's owner and its employee both get acme's
/// events on a watch that names nothing; neither is handed another account's
/// private repository, which is published first.
#[tokio::test]
async fn a_watch_naming_no_account_carries_every_account_the_caller_is_inside_and_no_other() {
    let rig = Rig::new();
    rig.repo("beta", "secret", Visibility::Private);
    rig.enroll(&Rig::owner(), "bob").await.expect("enroll bob");
    for caller in [Rig::owner(), Rig::caller("bob")] {
        let mut mine = rig
            .watch(&caller, &[])
            .await
            .expect("a person's own watch is accepted");
        rig.created_in("beta", "secret");
        rig.created(REPO);
        assert_eq!(
            next_repo_event(&mut mine).await.as_deref(),
            Some("acme/app"),
            "{}'s own watch",
            caller.principal_id
        );
    }
}

/// ★ **A grant revoked mid-stream stops the very next delivery** on a watch
/// that names no account: the boundary is decided per event, not once.
#[tokio::test]
async fn a_grant_revoked_mid_stream_stops_the_next_delivery_of_an_own_watch() {
    let rig = Rig::new();
    rig.repo("beta", "secret", Visibility::Private);
    let kim = rig.principal("kim");
    rig.plane
        .grant("kim", "beta", "", GrantLevel::Read)
        .expect("kim reads beta");
    let mut mine = rig.watch(&kim, &[]).await.expect("kim's own watch");
    rig.created_in("beta", "secret");
    assert_eq!(
        next_repo_event(&mut mine).await.as_deref(),
        Some("beta/secret")
    );

    rig.plane
        .revoke("kim", "beta", "")
        .expect("revoke kim's read");
    rig.created_in("beta", "secret");
    rig.created_in("kim", "own");
    assert_eq!(
        next_repo_event(&mut mine).await.as_deref(),
        Some("kim/own"),
        "an event of an account kim is no longer inside was delivered after the revoke"
    );
}

/// The stream's own bookkeeping reaches everybody (its `StreamOpened` comes
/// first), and an event about no repository — a key used for `auth` — reaches
/// a watch naming no account only when the key is the caller's own.
#[tokio::test]
async fn an_own_watch_carries_its_prologue_and_only_the_callers_own_key_use() {
    let rig = Rig::new();
    let kim = rig.principal("kim");
    let mut mine = rig.watch(&kim, &[]).await.expect("kim's own watch");
    let first = tokio::time::timeout(Duration::from_secs(1), mine.next())
        .await
        .expect("the prologue arrives")
        .expect("a first item")
        .expect("not a refusal");
    assert_eq!(first.kind, EventKind::StreamOpened as i32, "{first:?}");

    for who in ["acme", "kim"] {
        rig.bus.publish(event::Payload::KeyUsed(KeyUsed {
            principal_id: who.into(),
            fingerprint: format!("SHA256:{who}"),
            operation: "auth".into(),
            repo: None,
        }));
    }
    let next = tokio::time::timeout(Duration::from_secs(1), mine.next())
        .await
        .expect("an event arrives")
        .expect("an item")
        .expect("not a refusal");
    match next.payload {
        Some(event::Payload::KeyUsed(used)) => assert_eq!(used.principal_id, "kim"),
        other => panic!("expected kim's own key use first, got {other:?}"),
    }
}

/// **A name query never widens the listing**: an outsider searching an
/// account sees only its public repositories that match — the private one
/// that matches is not there (2026-10-04).
#[tokio::test]
async fn a_name_query_narrows_an_outsiders_listing_and_never_widens_it() {
    let rig = Rig::new().with_a_public_repo();
    let stranger = rig.principal("stranger");
    let search = |query: &str| {
        let mut request = Request::new(ListReposRequest {
            namespace: ACCOUNT.into(),
            query: query.into(),
            ..Default::default()
        });
        request.extensions_mut().insert(stranger.clone());
        request
    };
    let names = |r: ListReposResponse| -> Vec<String> {
        r.repos
            .into_iter()
            .map(|r| r.path.expect("path").repo)
            .collect()
    };
    let open = rig
        .repos
        .list(search("OP"))
        .await
        .expect("listed")
        .into_inner();
    assert_eq!(names(open), vec![OPEN.to_owned()]);
    let private = rig
        .repos
        .list(search("app"))
        .await
        .expect("listed")
        .into_inner();
    assert!(
        names(private).is_empty(),
        "a private repository matched for an outsider"
    );
}

// ── rule 3: an enterprise account is one that enrolled employees ────────────

/// ★ **The account admin enrolls NEW employees**, and that is what makes the
/// account an enterprise account. A member reads the account and may not make
/// anything public or enroll anyone; an outsider may not enroll either.
#[tokio::test]
async fn the_account_admin_enrolls_new_employees_who_may_not_make_anything_public() {
    let rig = Rig::new();
    let stranger = rig.principal("stranger");
    let before = rig.namespaces(&Rig::owner()).await.expect("list");
    assert_eq!(
        before
            .iter()
            .find(|ns| ns.name == ACCOUNT)
            .expect("acme")
            .employees,
        0,
        "an account is not enterprise until it enrolls somebody"
    );

    let bob = rig
        .enroll(&Rig::owner(), "bob")
        .await
        .expect("the admin enrolls bob");
    assert_eq!(bob.id, "bob");
    assert_eq!(bob.kind, PrincipalKind::User as i32);
    let after = rig.namespaces(&Rig::owner()).await.expect("list");
    assert_eq!(
        after
            .iter()
            .find(|ns| ns.name == ACCOUNT)
            .expect("acme")
            .employees,
        1,
        "the account is enterprise now, by enrolling — nothing was set"
    );
    let listed = rig
        .accounts
        .list_employees(with(
            &Rig::owner(),
            ListEmployeesRequest {
                account: ACCOUNT.into(),
            },
        ))
        .await
        .expect("the admin lists its employees")
        .into_inner()
        .employees;
    assert_eq!(
        listed.iter().map(|p| p.id.as_str()).collect::<Vec<_>>(),
        vec!["bob"]
    );

    // A member reads the account's private repository …
    let member = Rig::caller("bob");
    let got = rig
        .repos
        .get(with(
            &member,
            GetRepoRequest {
                path: Some(RepoPath::new(ACCOUNT, REPO)),
            },
        ))
        .await;
    assert!(got.is_ok(), "a member reads its account: {got:?}");
    // … and may not make anything public, change the settings, or enroll.
    let refused = rig
        .set(&member, Visibility::Public)
        .await
        .expect_err("a member publishes");
    assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
    let refused = rig
        .settings(&member, Some(true), None)
        .await
        .expect_err("a member makes the namespace public");
    assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
    for who in [&member, &stranger] {
        let refused = rig
            .enroll(who, "eve")
            .await
            .expect_err("only the account admin enrolls");
        assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
    }
    assert_eq!(rig.visibility(), Visibility::Private as i32);
}

/// ★ **An existing principal can never become an employee** — it might own
/// repositories already, a risk for the company — and neither can an id that
/// names an existing namespace, which would make the employee its admin.
#[tokio::test]
async fn an_existing_principal_or_namespace_cannot_become_an_employee() {
    let rig = Rig::new();
    rig.principal("carol");
    let refused = rig
        .enroll(&Rig::owner(), "carol")
        .await
        .expect_err("carol already exists");
    assert_eq!(refused.code(), Code::AlreadyExists, "{refused:?}");
    assert!(refused.message().contains("carol"), "{refused:?}");

    rig.plane
        .create_namespace("zed", "")
        .expect("a namespace with no principal");
    let refused = rig
        .enroll(&Rig::owner(), "zed")
        .await
        .expect_err("zed names a namespace");
    assert_eq!(refused.code(), Code::AlreadyExists, "{refused:?}");

    let listed = rig
        .accounts
        .list_employees(with(
            &Rig::owner(),
            ListEmployeesRequest {
                account: ACCOUNT.into(),
            },
        ))
        .await
        .expect("list")
        .into_inner()
        .employees;
    assert!(
        listed.is_empty(),
        "a refused enrolment left an employee: {listed:?}"
    );
}

// ── rule 4: a private namespace shows an outsider only its public repos ────

/// ★ **To an outsider a private namespace is its public repositories and
/// nothing else**, a public one is reachable by its direct path, and a private
/// namespace holding no public repository answers exactly as one that does not
/// exist.
#[tokio::test]
async fn a_private_namespace_shows_an_outsider_only_its_public_repositories() {
    let rig = Rig::new().with_a_public_repo();
    rig.repo("quiet", "hidden", Visibility::Private);
    let stranger = rig.principal("stranger");

    assert_eq!(
        rig.list(&stranger, ACCOUNT)
            .await
            .expect("an outsider lists acme"),
        vec![OPEN.to_owned()],
        "an outsider was shown a private repository, or not the public one"
    );
    let direct = rig
        .repos
        .get(with(
            &stranger,
            GetRepoRequest {
                path: Some(RepoPath::new(ACCOUNT, OPEN)),
            },
        ))
        .await;
    assert!(
        direct.is_ok(),
        "a public repository by its direct path: {direct:?}"
    );
    let hidden = rig
        .repos
        .get(with(
            &stranger,
            GetRepoRequest {
                path: Some(RepoPath::new(ACCOUNT, REPO)),
            },
        ))
        .await;
    assert!(
        hidden.is_err(),
        "a private repository to an outsider: {hidden:?}"
    );

    let quiet = rig.list(&stranger, "quiet").await;
    let ghost = rig.list(&stranger, "ghost").await;
    assert_eq!(
        format!("{quiet:?}"),
        format!("{ghost:?}"),
        "a private namespace without a public repository is told apart from one that does not \
         exist"
    );
    let seen = rig.namespaces(&stranger).await.expect("list");
    assert!(
        seen.iter()
            .all(|ns| ns.name != "quiet" && ns.name != ACCOUNT),
        "private namespaces were listed to an outsider: {seen:?}"
    );
}

// ── "Add users" (owner, 2026-10-04): the same enrolment, with room ─────────

const GIB: u64 = 1 << 30;

/// ★ **Number = 1: that NAMED user is created as an employee, holding its
/// room** — the server's default 10 GiB when none is asked for.
#[tokio::test]
async fn one_named_user_is_enrolled_holding_its_room() {
    let rig = Rig::new();
    let added = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                count: 1,
                name: "eve".into(),
                ..Default::default()
            },
        )
        .await
        .expect("one named user");
    let eve = added.principal.expect("the named employee comes back");
    assert_eq!(eve.id, "eve");
    assert_eq!(
        added.room_bytes,
        10 * GIB,
        "the default room per user is 10 GiB"
    );
    assert_eq!(
        added.need_bytes,
        10 * GIB,
        "one user asks its room of the account"
    );
    assert_eq!(
        (added.seats, added.seats_open),
        (0, 0),
        "a named user is not a seat"
    );
    assert!(!added.unchanged);
    let listed = rig.employees(&Rig::owner()).await.expect("list");
    assert_eq!(
        listed
            .employees
            .iter()
            .map(|p| p.id.as_str())
            .collect::<Vec<_>>(),
        vec!["eve"]
    );

    // Raised, not lowered.
    let more = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                name: "max".into(),
                room_bytes: 25 * GIB,
                ..Default::default()
            },
        )
        .await
        .expect("a raised room");
    assert_eq!(more.room_bytes, 25 * GIB);
    let less = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                name: "tiny".into(),
                room_bytes: GIB,
                ..Default::default()
            },
        )
        .await
        .expect_err("less than 10 GiB per user");
    assert_eq!(less.code(), Code::InvalidArgument, "{less:?}");
    assert!(
        less.message().contains("10 GiB"),
        "the refusal names the floor: {}",
        less.message()
    );

    // A name is required for ONE user, and an existing one is still refused.
    let nameless = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                count: 1,
                ..Default::default()
            },
        )
        .await
        .expect_err("one user with no name");
    assert_eq!(nameless.code(), Code::InvalidArgument, "{nameless:?}");
    let again = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                name: "eve".into(),
                ..Default::default()
            },
        )
        .await
        .expect_err("an existing user");
    assert_eq!(again.code(), Code::AlreadyExists, "{again:?}");
}

/// ★ **Number > 1: NO names — that many SEATS for the account**, each with
/// the room asked for; nobody is created until a seat is filled.
#[tokio::test]
async fn ten_users_are_ten_seats_with_no_names() {
    let rig = Rig::new();
    let added = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                count: 10,
                room_bytes: 20 * GIB,
                ..Default::default()
            },
        )
        .await
        .expect("ten seats");
    assert!(
        added.principal.is_none(),
        "seats name nobody: {:?}",
        added.principal
    );
    assert_eq!((added.seats, added.seats_open), (10, 10));
    assert_eq!(added.room_bytes, 20 * GIB);
    assert_eq!(added.need_bytes, 200 * GIB, "count × room");
    let listed = rig.employees(&Rig::owner()).await.expect("list");
    assert!(
        listed.employees.is_empty(),
        "a seat is not an employee: {:?}",
        listed.employees
    );
    assert_eq!((listed.seats, listed.seats_open), (10, 10));

    // Names are not asked when the number is > 1 — and refused when sent.
    let named = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                count: 3,
                name: "bob".into(),
                ..Default::default()
            },
        )
        .await
        .expect_err("three users and one name");
    assert_eq!(named.code(), Code::InvalidArgument, "{named:?}");
    // Up to 10 000.
    let too_many = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                count: 10_001,
                ..Default::default()
            },
        )
        .await
        .expect_err("10 001 users");
    assert_eq!(too_many.code(), Code::InvalidArgument, "{too_many:?}");
    assert!(
        too_many.message().contains("10000") || too_many.message().contains("10 000"),
        "{}",
        too_many.message()
    );
    rig.add(
        &Rig::owner(),
        EnrollEmployeeRequest {
            count: 10_000,
            ..Default::default()
        },
    )
    .await
    .expect("10 000 users is the most");

    // Only the account admin adds users.
    let stranger = rig.principal("stranger");
    let refused = rig
        .add(
            &stranger,
            EnrollEmployeeRequest {
                count: 5,
                ..Default::default()
            },
        )
        .await
        .expect_err("a stranger adds seats");
    assert_eq!(refused.code(), Code::PermissionDenied, "{refused:?}");
}

/// ★ **A seat is filled by an invite** (the seat claim — later also a batch
/// import): the new named employee takes ONE seat and that seat's room.
#[tokio::test]
async fn an_invite_fills_one_seat_and_takes_its_room() {
    let rig = Rig::new();
    let none = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                from_seat: true,
                name: "kim".into(),
                ..Default::default()
            },
        )
        .await
        .expect_err("a claim with no seat");
    assert_eq!(none.code(), Code::FailedPrecondition, "{none:?}");
    assert!(none.message().contains("seat"), "{}", none.message());
    assert!(
        rig.plane.principal_by_name("kim").expect("ask").is_none(),
        "a refused claim created kim"
    );

    rig.add(
        &Rig::owner(),
        EnrollEmployeeRequest {
            count: 3,
            room_bytes: 15 * GIB,
            ..Default::default()
        },
    )
    .await
    .expect("three seats");
    let kim = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                from_seat: true,
                name: "kim".into(),
                ..Default::default()
            },
        )
        .await
        .expect("an invite fills a seat");
    assert_eq!(kim.principal.as_ref().map(|p| p.id.as_str()), Some("kim"));
    assert_eq!(
        kim.room_bytes,
        15 * GIB,
        "the employee holds the seat's room"
    );
    assert_eq!(
        kim.need_bytes, 0,
        "a seat's room was set aside with the seat"
    );
    assert_eq!((kim.seats, kim.seats_open), (3, 2));
    let listed = rig.employees(&Rig::owner()).await.expect("list");
    assert_eq!(
        listed
            .employees
            .iter()
            .map(|p| p.id.as_str())
            .collect::<Vec<_>>(),
        vec!["kim"]
    );
    assert_eq!((listed.seats, listed.seats_open), (3, 2));

    // A claim names its user, and the seat decides the room.
    let nameless = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                from_seat: true,
                ..Default::default()
            },
        )
        .await
        .expect_err("a claim with no name");
    assert_eq!(nameless.code(), Code::InvalidArgument, "{nameless:?}");
    let sized = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                from_seat: true,
                name: "lou".into(),
                room_bytes: 99 * GIB,
                ..Default::default()
            },
        )
        .await
        .expect_err("a claim that sizes its own room");
    assert_eq!(sized.code(), Code::InvalidArgument, "{sized:?}");
}

/// ★ **An order is granted ONCE.** The seller retries a grant after a
/// timeout; the second answer is the first grant, and nothing doubles.
#[tokio::test]
async fn a_retried_order_is_granted_once() {
    let rig = Rig::new();
    let first = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                count: 4,
                order: "gunnar/acme/o-1".into(),
                ..Default::default()
            },
        )
        .await
        .expect("four seats");
    let again = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                count: 4,
                order: "gunnar/acme/o-1".into(),
                ..Default::default()
            },
        )
        .await
        .expect("the same order again");
    assert!(again.unchanged, "a retried order wrote again");
    assert_eq!(
        (again.seats, again.seats_open),
        (first.seats, first.seats_open),
        "seats doubled on a retry"
    );

    let named = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                name: "ann".into(),
                order: "gunnar/acme/o-2".into(),
                ..Default::default()
            },
        )
        .await
        .expect("ann");
    let named_again = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                name: "ann".into(),
                order: "gunnar/acme/o-2".into(),
                ..Default::default()
            },
        )
        .await
        .expect("ann's order again is not 'ann exists'");
    assert!(named_again.unchanged);
    assert_eq!(
        named_again.principal.map(|p| p.id),
        named.principal.map(|p| p.id)
    );
}

/// ★ **The room comes from what the account already has.** Short → refused,
/// naming how much is missing, and NOTHING is written; a dry run asks the
/// same question and answers with the shortfall instead.
#[cfg(feature = "metered")]
#[tokio::test]
async fn a_short_account_is_refused_naming_the_missing_room() {
    let rig = Rig::new();
    // 50 GiB held, 20 GiB stored: 30 GiB unused.
    rig.room(50 * GIB, 20 * GIB);

    let ask = EnrollEmployeeRequest {
        count: 4,
        room_bytes: 10 * GIB,
        ..Default::default()
    };
    let dry = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                dry_run: true,
                ..ask.clone()
            },
        )
        .await
        .expect("a dry run answers");
    assert!(dry.unchanged, "a dry run wrote");
    assert_eq!(dry.need_bytes, 40 * GIB);
    assert_eq!(dry.unused_bytes, Some(30 * GIB));
    assert_eq!(
        dry.shortfall_bytes,
        10 * GIB,
        "4 × 10 GiB of 30 GiB unused is 10 GiB short"
    );

    let short = rig.add(&Rig::owner(), ask).await.expect_err("40 GiB of 30");
    assert_eq!(short.code(), Code::FailedPrecondition, "{short:?}");
    assert!(
        short.message().contains("10 GiB"),
        "the refusal names what is missing: {}",
        short.message()
    );
    assert!(short.message().contains("short"), "{}", short.message());
    let listed = rig.employees(&Rig::owner()).await.expect("list");
    assert_eq!(
        (listed.seats, listed.seats_open),
        (0, 0),
        "a refused order left seats behind"
    );

    // What fits is granted, and what it holds is no longer unused.
    rig.add(
        &Rig::owner(),
        EnrollEmployeeRequest {
            count: 2,
            room_bytes: 10 * GIB,
            ..Default::default()
        },
    )
    .await
    .expect("2 × 10 GiB of 30");
    let left = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                name: "eve".into(),
                dry_run: true,
                ..Default::default()
            },
        )
        .await
        .expect("dry");
    assert_eq!(
        left.unused_bytes,
        Some(10 * GIB),
        "the seats' room is promised, not unused"
    );
    assert_eq!(left.shortfall_bytes, 0);
    rig.add(
        &Rig::owner(),
        EnrollEmployeeRequest {
            name: "eve".into(),
            ..Default::default()
        },
    )
    .await
    .expect("the last 10 GiB");
    let full = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                name: "max".into(),
                ..Default::default()
            },
        )
        .await
        .expect_err("nothing unused");
    assert_eq!(full.code(), Code::FailedPrecondition, "{full:?}");
    // A seat claim needs no room: the seat already holds it.
    rig.add(
        &Rig::owner(),
        EnrollEmployeeRequest {
            from_seat: true,
            name: "kim".into(),
            ..Default::default()
        },
    )
    .await
    .expect("a claim on a full account");
}

/// ★ **What the account stores counts in full** (rung 1, 2026-10-04: the paid
/// order bought exactly "30 GiB short" for an account storing 1.5 MiB with all
/// its room promised, and the grant then found it 1.5 MiB short). The
/// shortfall is everything a sale must ADD for the ask to fit: the stored
/// bytes past what is free are in it, never hidden by a free room of zero.
#[cfg(feature = "metered")]
#[tokio::test]
async fn the_shortfall_counts_what_the_account_stores_past_its_free_room() {
    let rig = Rig::new();
    rig.room(20 * GIB, 0);
    rig.add(
        &Rig::owner(),
        EnrollEmployeeRequest {
            count: 1,
            name: "dora".into(),
            ..Default::default()
        },
    )
    .await
    .expect("dora takes 10 GiB of 20");
    rig.room(20 * GIB, 10 * GIB + (3 << 20)); // the account stored 10 GiB and 3 MiB since
    let dry = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                count: 3,
                dry_run: true,
                ..Default::default()
            },
        )
        .await
        .expect("a dry run");
    assert_eq!(dry.unused_bytes, Some(0));
    assert_eq!(
        dry.shortfall_bytes,
        30 * GIB + (3 << 20),
        "the 3 MiB stored past the free room were hidden"
    );
}

/// ★ **At an estate that SELLS room, a person's account with no fact holds none** (the
/// free-room leak's last window, 2026-10-08): until the shop's fact lands, an employee is
/// refused for room; the seller's own account and every account at an estate that does not
/// sell (a sold private estate, the open build: nothing fed) keep "no fact is uncapped".
#[cfg(feature = "metered")]
#[tokio::test]
async fn at_a_selling_estate_a_person_with_no_fact_holds_no_room_and_elsewhere_is_uncapped() {
    let plane = Arc::new(MemoryControlPlane::with_repo(ACCOUNT, REPO));
    plane
        .add_principal(ACCOUNT, ACCOUNT, PrincipalKind::User)
        .expect("the account's owner");
    plane.set_usage(NamespaceUsage {
        namespace: ACCOUNT.to_owned(),
        pack_bytes: 0,
        ..Default::default()
    });
    let control: Arc<dyn ControlPlane> = plane.clone();
    let ctx = ServiceContext::new(control, Arc::new(EventBus::new(8)), ServerConfig::default());
    let selling = ctx.sells_room.clone();
    let accounts = AccountsService::new(ctx);
    let ask = || EnrollEmployeeRequest {
        account: ACCOUNT.into(),
        count: 1,
        name: "dora".into(),
        dry_run: true,
        ..Default::default()
    };
    // A sold estate / the open build (nothing fed): no fact, no cap.
    let free = accounts
        .enroll_employee(with(&Rig::owner(), ask()))
        .await
        .expect("uncapped")
        .into_inner();
    assert_eq!(
        (free.unused_bytes, free.shortfall_bytes),
        (None, 0),
        "{free:?}"
    );
    // A selling shop: no room until the fact lands — short by the whole ask.
    selling.store(true, std::sync::atomic::Ordering::Relaxed);
    let short = accounts
        .enroll_employee(with(&Rig::owner(), ask()))
        .await
        .expect("a dry run answers")
        .into_inner();
    assert_eq!(short.unused_bytes, Some(0), "{short:?}");
    assert!(
        short.shortfall_bytes >= 10 * GIB,
        "the whole ask is short: {short:?}"
    );
    let refused = accounts
        .enroll_employee(with(
            &Rig::owner(),
            EnrollEmployeeRequest {
                account: ACCOUNT.into(),
                count: 1,
                name: "dora".into(),
                ..Default::default()
            },
        ))
        .await
        .expect_err("no room, no employee");
    assert_eq!(refused.code(), Code::FailedPrecondition, "{refused:?}");
    // The seller's own account keeps today's rule.
    plane.grant_server(ACCOUNT, GrantLevel::Admin);
    let seller = accounts
        .enroll_employee(with(&Rig::owner(), ask()))
        .await
        .expect("the seller's own")
        .into_inner();
    assert_eq!(seller.shortfall_bytes, 0, "{seller:?}");
}

/// ★ **The seller's grant under a paid order is not refused for room**: the
/// customer paid for exactly the shortfall the dry run named; if the account
/// stored a little more meanwhile, the users are still theirs. An account
/// admin naming an order is asked like anybody else.
#[cfg(feature = "metered")]
#[tokio::test]
async fn a_paid_grant_from_the_seller_is_not_refused_for_room_the_account_used_meanwhile() {
    let rig = Rig::new();
    rig.room(40 * GIB, 10 * GIB + (2 << 20)); // paid for 3 x 10 GiB beside 10 GiB stored, then stored 2 MiB more
    let ask = EnrollEmployeeRequest {
        count: 3,
        order: "gunnar/acme/2026-10-04+users".into(),
        ..Default::default()
    };
    let refused = rig
        .add(&Rig::owner(), ask.clone())
        .await
        .expect_err("the account admin is asked");
    assert_eq!(refused.code(), Code::FailedPrecondition, "{refused:?}");
    rig.plane.grant_server("monetize", GrantLevel::Admin);
    let seller = rig.principal("monetize");
    let granted = rig
        .add(&seller, ask)
        .await
        .expect("the seller's paid grant");
    assert_eq!((granted.seats, granted.seats_open), (3, 3));
}

/// ★ **The new named user's invite comes with the enrolment** (2026-10-04,
/// the circle analysis: a seat filled from the console, and a user bought
/// through checkout, must each get the invite the admin's path makes). One
/// call: the employee and a pending passkey enrolment on exactly it; the
/// secret comes back once. Seats, dry runs and a repeated order make none.
#[tokio::test]
async fn a_named_user_is_enrolled_with_its_invite_in_the_same_call() {
    let rig = Rig::new();
    let eve = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                name: "eve".into(),
                invite: true,
                order: "o-eve".into(),
                ..Default::default()
            },
        )
        .await
        .expect("eve and her invite");
    assert!(
        !eve.invite_id.is_empty() && !eve.invite_secret.is_empty(),
        "no invite came back: {eve:?}"
    );
    assert!(eve.invite_expires_at_unix_ms > 0);
    let invites = rig.invites();
    assert_eq!(
        invites.iter().filter(|(p, _)| p == "eve").count(),
        1,
        "{invites:?}"
    );
    assert_eq!(
        invites
            .iter()
            .find(|(p, _)| p == "eve")
            .map(|(_, by)| by.as_str()),
        Some(ACCOUNT),
        "the invite is the caller's"
    );

    let again = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                name: "eve".into(),
                invite: true,
                order: "o-eve".into(),
                ..Default::default()
            },
        )
        .await
        .expect("the same order again");
    assert!(
        again.unchanged && again.invite_secret.is_empty(),
        "a repeated order made a second invite: {again:?}"
    );
    let seats = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                count: 3,
                invite: true,
                ..Default::default()
            },
        )
        .await
        .expect("seats");
    assert!(
        seats.invite_id.is_empty(),
        "an invite for nobody: {seats:?}"
    );
    let dry = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                name: "max".into(),
                invite: true,
                dry_run: true,
                ..Default::default()
            },
        )
        .await
        .expect("dry");
    assert!(dry.invite_id.is_empty(), "a dry run made an invite");
    assert_eq!(rig.invites().len(), 1);

    // ★ A SEAT is filled by an invite from the console: the claim makes the
    // new user, takes the seat, and hands back the user's invite.
    let kim = rig
        .add(
            &Rig::owner(),
            EnrollEmployeeRequest {
                from_seat: true,
                name: "kim".into(),
                invite: true,
                ..Default::default()
            },
        )
        .await
        .expect("kim fills a seat and is invited");
    assert_eq!((kim.seats, kim.seats_open), (3, 2));
    assert!(
        !kim.invite_secret.is_empty(),
        "the seat's user has no invite: {kim:?}"
    );
    assert!(rig.invites().iter().any(|(p, _)| p == "kim"));
}