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"));
}