crates/gunnar-ui-server/src/tenant_import.rs
//! **An ordinary user imports a repository into their OWN namespace**, driven
//! end to end against a gunnar that keeps a grant table, a usage ledger and a
//! mirror queue.
//!
//! Tests only, and its own module for the reason [`crate::mirroring`] is: what
//! is asserted is a property of the whole path — session → principal → grants →
//! room → limits → `Mirror.Add` — and not of one handler. `mirroring` drives the
//! same routes as the ADMINISTRATOR; this drives them as a viewer, which is the
//! rung a paying customer lands on.
//!
//! # gunnar decides, and what it HEARD is the assertion
//!
//! Since 2026-10-04 (thin layer) the console signs every mirror call AS THE
//! PERSON, and the fake below decides the way gunnar does
//! (`gunnar-grpc/src/service/mirror.rs`): `Mirror.Add` creating a repository
//! is `Admin` over the namespace, `SetCredential` and `Remove` are `Admin` on
//! the repository, `Sync` is `Write`, `Status` and `Usage.Read` are `Read` — a
//! principal is admin of its own namespace, and grants count directly or
//! through a group. `heard` holds the calls gunnar ACCEPTED, `refused` the ones
//! it refused, each with the principal whose key signed it.
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use gunnar_forge::testing::MockServer;
use tonic::{Request, Response, Status};
use tower::ServiceExt as _;
use crate::api::mirror_gate::{Imports, Limits};
use crate::forges::ForgeApps;
use crate::pb;
use crate::role::Role;
/// The console's own key: the only one the fake answers mutations for.
const CONSOLE_SEED: [u8; 32] = [41u8; 32];
/// A token distinctive enough that a `contains` over a body or a log means
/// something.
const TOKEN: &str = "tok_QQ-one-way-only-QQ-7e1d55";
fn console_signer() -> crate::signer::Signer {
crate::signer::Signer::new(ssh_key::PrivateKey::from(
ssh_key::private::Ed25519Keypair::from_seed(&CONSOLE_SEED),
))
.expect("a fixed seed makes a signer")
}
fn fingerprint_of<T>(request: &Request<T>) -> String {
request
.metadata()
.get(crate::signer::CREDENTIAL_METADATA_KEY)
.and_then(|value| value.to_str().ok())
.and_then(|line| line.split_whitespace().nth(1))
.unwrap_or("(unsigned)")
.to_owned()
}
fn key(path: &pb::RepoPath) -> String {
format!("{}/{}", path.namespace, path.repo)
}
// ───────────────────────────────────────────────────────────────────────────
// The fake
// ───────────────────────────────────────────────────────────────────────────
#[derive(Clone, Default)]
#[allow(clippy::type_complexity)]
struct Fake {
/// Every mirror-service call, in order.
calls: Arc<Mutex<Vec<String>>>,
/// Every secret handed to SetCredential.
secrets: Arc<Mutex<Vec<String>>>,
mirrors: Arc<Mutex<BTreeMap<String, pb::MirrorConfig>>>,
grants: Arc<Mutex<Vec<pb::Grant>>>,
/// id → kind.
principals: Arc<Mutex<BTreeMap<String, i32>>>,
/// group id → direct member ids.
members: Arc<Mutex<BTreeMap<String, Vec<String>>>>,
/// namespace → (held, cap). Absent: uncapped and empty.
room: Arc<Mutex<BTreeMap<String, (u64, Option<u64>)>>>,
/// When true, `Usage.Read` is refused — the appliance cannot say.
usage_down: Arc<Mutex<bool>>,
/// tenant-key fingerprint → principal.
keys: Arc<Mutex<BTreeMap<String, String>>>,
/// Ordinary repositories (not mirrors) that already exist, `<ns>/<name>`.
plain: Arc<Mutex<Vec<String>>>,
/// **Calls gunnar would have REFUSED** — a one-repository read of a
/// repository that does not exist is `PermissionDenied` at gunnar (it does
/// not say which), and every refusal is a WARN line in main's log. The
/// ordinary import path must make none.
refused: Arc<Mutex<Vec<String>>>,
/// Every call gunnar ACCEPTED, as `<Method> by <who>`.
signed: Arc<Mutex<Vec<String>>>,
}
impl Fake {
fn note(&self, line: impl Into<String>) {
self.calls.lock().expect("calls").push(line.into());
}
fn heard(&self) -> Vec<String> {
self.calls.lock().expect("calls").clone()
}
fn heard_any(&self, prefix: &str) -> bool {
self.heard().iter().any(|c| c.starts_with(prefix))
}
fn console_only<T>(&self, request: &Request<T>) -> Result<(), Status> {
if fingerprint_of(request) != console_signer().fingerprint() {
return Err(Status::permission_denied(
"only the console's key mutates here",
));
}
Ok(())
}
/// Whose key signed this: `console`, the enrolled principal, or `stranger`.
fn who<T>(&self, request: &Request<T>) -> String {
let fingerprint = fingerprint_of(request);
if fingerprint == console_signer().fingerprint() {
return "console".to_owned();
}
self.keys
.lock()
.expect("keys")
.get(&fingerprint)
.cloned()
.unwrap_or_else(|| "stranger".to_owned())
}
/// The principal and every group it is a member of.
fn selves(&self, principal: &str) -> Vec<String> {
let mut selves = vec![principal.to_owned()];
for (group, members) in self.members.lock().expect("members").iter() {
if members.iter().any(|m| m == principal) {
selves.push(group.clone());
}
}
selves
}
/// gunnar's `require`: the console's server-wide Admin; a principal's own
/// namespace; a grant at `level` or above on the namespace (repo empty) or
/// on this repository, held directly or through a group. A refusal is
/// noted in `refused` with who asked.
fn require<T>(
&self,
request: &Request<T>,
method: &str,
ns: &str,
repo: Option<&str>,
level: pb::GrantLevel,
) -> Result<String, Status> {
let who = self.who(request);
if who == "console" {
self.signed.lock().expect("signed").push(format!("{method} by {who}"));
return Ok(who);
}
let selves = self.selves(&who);
let allowed = who == ns
|| self.grants.lock().expect("grants").iter().any(|g| {
selves.contains(&g.principal_id)
&& g.namespace == ns
&& (g.repo.is_empty() || Some(g.repo.as_str()) == repo)
&& g.level >= level as i32
});
if allowed {
self.signed.lock().expect("signed").push(format!("{method} by {who}"));
return Ok(who);
}
let on = match repo {
Some(repo) => format!("repo:{ns}/{repo}"),
None => format!("namespace:{ns}"),
};
self.refused
.lock()
.expect("refused")
.push(format!("{method} {on} by {who}"));
Err(Status::permission_denied(format!(
"{method}: principal {who} has no {} grant on {on}",
match level {
pb::GrantLevel::Admin => "admin",
pb::GrantLevel::Write => "write",
_ => "read",
}
)))
}
fn signed_for(&self, method: &str) -> Vec<String> {
self.signed
.lock()
.expect("signed")
.iter()
.filter(|c| c.starts_with(method))
.cloned()
.collect()
}
/// gunnar's decision for `who`, without noting anything — what
/// `Access.Check` answers.
fn decides(&self, who: &str, ns: &str, repo: Option<&str>, level: i32) -> bool {
if who == "console" {
return true;
}
let selves = self.selves(who);
who == ns
|| self.grants.lock().expect("grants").iter().any(|g| {
selves.contains(&g.principal_id)
&& g.namespace == ns
&& (g.repo.is_empty() || Some(g.repo.as_str()) == repo)
&& g.level >= level
})
}
fn refused_any(&self, prefix: &str) -> bool {
self.refused
.lock()
.expect("refused")
.iter()
.any(|c| c.starts_with(prefix))
}
fn user(&self, id: &str) {
self.principals
.lock()
.expect("principals")
.insert(id.to_owned(), pb::PrincipalKind::User as i32);
}
fn group(&self, id: &str, members: &[&str]) {
self.principals
.lock()
.expect("principals")
.insert(id.to_owned(), pb::PrincipalKind::Group as i32);
self.members.lock().expect("members").insert(
id.to_owned(),
members.iter().map(|m| (*m).to_owned()).collect(),
);
}
fn grant(&self, principal: &str, ns: &str, repo: &str, level: pb::GrantLevel) {
self.grants.lock().expect("grants").push(pb::Grant {
principal_id: principal.into(),
namespace: ns.into(),
repo: repo.into(),
level: level as i32,
granted_at_unix_ms: 1,
});
}
fn room(&self, ns: &str, held: u64, cap: Option<u64>) {
self.room
.lock()
.expect("room")
.insert(ns.to_owned(), (held, cap));
}
/// Settle a path's job, the way the queue's worker would — with gunnar's
/// structured reason when the namespace's room stopped it.
fn finish(&self, path: &str, state: pb::MirrorJobState, error: &str) {
self.finish_because(path, state, error, pb::MirrorRefusal::Unspecified);
}
fn finish_because(&self, path: &str, state: pb::MirrorJobState, error: &str, why: pb::MirrorRefusal) {
if let Some(m) = self.mirrors.lock().expect("mirrors").get_mut(path) {
if let Some(job) = m.job.as_mut() {
job.state = state as i32;
job.error = error.to_owned();
job.refused_reason = why as i32;
}
}
}
fn already(&self, path: &str, remote: &str) {
let (ns, repo) = path.split_once('/').expect("two parts");
self.mirrors.lock().expect("mirrors").insert(
path.to_owned(),
pb::MirrorConfig {
repo: Some(crate::api::repo_path(ns, repo)),
remote_url: remote.to_owned(),
state: pb::MirrorState::Idle as i32,
..Default::default()
},
);
}
}
fn unmodelled(verb: &str) -> Status {
Status::unimplemented(format!("{verb} is not modelled by the tenant-import fake"))
}
#[tonic::async_trait]
impl pb::mirror_server::Mirror for Fake {
async fn add(
&self,
request: Request<pb::AddMirrorRequest>,
) -> Result<Response<pb::AddMirrorResponse>, Status> {
let path = request.get_ref().repo.clone().unwrap_or_default();
if request.get_ref().create_repo {
self.require(&request, "Mirror.Add", &path.namespace, None, pb::GrantLevel::Admin)?;
} else {
self.require(&request, "Mirror.Add", &path.namespace, Some(&path.repo), pb::GrantLevel::Admin)?;
}
let request = request.into_inner();
let name = key(&path);
// gunnar 739b5f1: creating over an existing repository that is not
// already a mirror is refused by name unless `adopt` says otherwise.
if request.create_repo
&& !request.adopt
&& self.plain.lock().expect("plain").contains(&name)
&& !self.mirrors.lock().expect("mirrors").contains_key(&name)
{
return Err(Status::failed_precondition(format!(
"{name} already exists and is not a mirror, so it is not taken over: add it with \
adopt to make it a mirror of {} (its next sync moves its refs to the upstream's)",
request.remote_url
)));
}
self.note(format!("Add {name} <- {}", request.remote_url));
let mut mirrors = self.mirrors.lock().expect("mirrors");
let entry = mirrors.entry(name).or_default();
entry.repo = Some(path);
entry.remote_url = request.remote_url;
entry.prune = request.prune;
entry.state = pb::MirrorState::Idle as i32;
Ok(Response::new(pb::AddMirrorResponse {
mirror: Some(entry.clone()),
}))
}
async fn set_credential(
&self,
request: Request<pb::SetMirrorCredentialRequest>,
) -> Result<Response<pb::SetMirrorCredentialResponse>, Status> {
let path = request.get_ref().repo.clone().unwrap_or_default();
self.require(&request, "Mirror.SetCredential", &path.namespace, Some(&path.repo), pb::GrantLevel::Admin)?;
let request = request.into_inner();
let name = key(&request.repo.clone().unwrap_or_default());
if !request.secret.is_empty() {
self.secrets
.lock()
.expect("secrets")
.push(request.secret.clone());
}
self.note(format!("SetCredential {name} {}", request.username));
let mut mirrors = self.mirrors.lock().expect("mirrors");
let entry = mirrors
.get_mut(&name)
.ok_or_else(|| Status::not_found("no mirror"))?;
entry.credential = request.kind;
entry.credential_username = request.username;
Ok(Response::new(pb::SetMirrorCredentialResponse {
mirror: Some(entry.clone()),
}))
}
async fn set_ca(
&self,
request: Request<pb::SetMirrorCaRequest>,
) -> Result<Response<pb::SetMirrorCaResponse>, Status> {
let path = request.get_ref().repo.clone().unwrap_or_default();
self.require(&request, "Mirror.SetCa", &path.namespace, Some(&path.repo), pb::GrantLevel::Admin)?;
let request = request.into_inner();
let name = key(&path);
let first = request.ca_pem.lines().next().unwrap_or_default().to_owned();
self.note(format!("SetCa {name} {first}"));
let mut mirrors = self.mirrors.lock().expect("mirrors");
let entry = mirrors
.get_mut(&name)
.ok_or_else(|| Status::not_found("no mirror"))?;
entry.ca_subject = if request.ca_pem.is_empty() { String::new() } else { "CN=Acme Private Root".to_owned() };
Ok(Response::new(pb::SetMirrorCaResponse {
mirror: Some(entry.clone()),
}))
}
async fn sync(
&self,
request: Request<pb::SyncMirrorRequest>,
) -> Result<Response<pb::SyncMirrorResponse>, Status> {
let path = request.get_ref().repo.clone().unwrap_or_default();
self.require(&request, "Mirror.Sync", &path.namespace, Some(&path.repo), pb::GrantLevel::Write)?;
let name = key(&path);
self.note(format!("Sync {name}"));
let mut mirrors = self.mirrors.lock().expect("mirrors");
let entry = mirrors
.get_mut(&name)
.ok_or_else(|| Status::not_found("no mirror"))?;
let live = entry.job.clone().filter(|j| {
matches!(
pb::MirrorJobState::try_from(j.state),
Ok(pb::MirrorJobState::Queued | pb::MirrorJobState::Running)
)
});
let job = live.unwrap_or(pb::MirrorJob {
job_id: format!("mirror-{name}-{}", entry.sync_count),
path: Some(path),
state: pb::MirrorJobState::Queued as i32,
..Default::default()
});
entry.sync_count += 1;
entry.job = Some(job.clone());
Ok(Response::new(pb::SyncMirrorResponse {
mirror: Some(entry.clone()),
refs_changed: 0,
updates: Vec::new(),
job: Some(job),
}))
}
async fn status(
&self,
request: Request<pb::MirrorStatusRequest>,
) -> Result<Response<pb::MirrorStatusResponse>, Status> {
// gunnar's `Read`: on the repository when one is named, on the
// namespace when only that is, and server-wide for neither.
let asked = request.get_ref().clone();
let wanted = asked.repo.as_ref().map(key);
let who = match (&asked.repo, asked.namespace.is_empty()) {
(Some(path), _) => {
let who = self.who(&request);
if who != "console" {
let exists = wanted.as_ref().is_some_and(|w| {
self.mirrors.lock().expect("mirrors").contains_key(w)
|| self.plain.lock().expect("plain").contains(w)
});
if !exists {
self.refused
.lock()
.expect("refused")
.push(format!("Status {}", key(path)));
return Err(Status::permission_denied(format!(
"principal has no read grant on repo:{}",
key(path)
)));
}
}
self.require(&request, "Mirror.Status", &path.namespace, Some(&path.repo), pb::GrantLevel::Read)?
}
(None, false) => {
self.require(&request, "Mirror.Status", &asked.namespace, None, pb::GrantLevel::Read)?
}
(None, true) => {
let who = self.who(&request);
if who != "console" {
return Err(Status::permission_denied("Mirror.Status of the whole box is a server reader's"));
}
who
}
};
let namespace = asked.namespace;
self.note(format!(
"Status {} by {}",
wanted.clone().unwrap_or_else(|| if namespace.is_empty() { "*".into() } else { format!("{namespace}/*") }),
who
));
let mirrors = self.mirrors.lock().expect("mirrors");
let out = mirrors
.iter()
.filter(|(name, _)| wanted.as_ref().is_none_or(|w| w == *name))
.filter(|(name, _)| namespace.is_empty() || name.split('/').next() == Some(namespace.as_str()))
.map(|(_, m)| m.clone())
.collect();
Ok(Response::new(pb::MirrorStatusResponse { mirrors: out }))
}
async fn remove(
&self,
request: Request<pb::RemoveMirrorRequest>,
) -> Result<Response<pb::RemoveMirrorResponse>, Status> {
let path = request.get_ref().repo.clone().unwrap_or_default();
self.require(&request, "Mirror.Remove", &path.namespace, Some(&path.repo), pb::GrantLevel::Admin)?;
let name = key(&path);
self.note(format!("Remove {name}"));
self.mirrors.lock().expect("mirrors").remove(&name);
Ok(Response::new(pb::RemoveMirrorResponse {}))
}
async fn set_replication(
&self,
_: Request<pb::SetReplicationRequest>,
) -> Result<Response<pb::ReplicationStatusResponse>, Status> {
Err(unmodelled("SetReplication"))
}
async fn remove_replication(
&self,
_: Request<pb::RemoveReplicationRequest>,
) -> Result<Response<pb::ReplicationStatusResponse>, Status> {
Err(unmodelled("RemoveReplication"))
}
async fn demote_replication(
&self,
_: Request<pb::DemoteReplicationRequest>,
) -> Result<Response<pb::ReplicationStatusResponse>, Status> {
Err(unmodelled("DemoteReplication"))
}
async fn set_role(
&self,
_: Request<pb::SetRoleRequest>,
) -> Result<Response<pb::ReplicationStatusResponse>, Status> {
Err(unmodelled("SetRole"))
}
async fn abdicate_replication(
&self,
_: Request<pb::AbdicateReplicationRequest>,
) -> Result<Response<pb::ReplicationStatusResponse>, Status> {
Err(unmodelled("AbdicateReplication"))
}
async fn replication_status(
&self,
_: Request<pb::ReplicationStatusRequest>,
) -> Result<Response<pb::ReplicationStatusResponse>, Status> {
Err(unmodelled("ReplicationStatus"))
}
async fn resync_replication(
&self,
_: Request<pb::ResyncReplicationRequest>,
) -> Result<Response<pb::ResyncReplicationResponse>, Status> {
Err(unmodelled("ResyncReplication"))
}
async fn flush_replication(
&self,
_: Request<pb::FlushReplicationRequest>,
) -> Result<Response<pb::FlushReplicationResponse>, Status> {
Err(unmodelled("FlushReplication"))
}
async fn log_head(
&self,
_: Request<pb::LogHeadRequest>,
) -> Result<Response<pb::LogHeadResponse>, Status> {
Err(unmodelled("LogHead"))
}
// `LogFetch` / `SnapshotFetch`: the generated default stubs answer Unimplemented.
async fn log_retain(
&self,
_: Request<pb::LogRetainRequest>,
) -> Result<Response<pb::LogHeadResponse>, Status> {
Err(unmodelled("LogRetain"))
}
}
#[tonic::async_trait]
impl pb::access_server::Access for Fake {
async fn check(
&self,
request: Request<pb::CheckRequest>,
) -> Result<Response<pb::CheckResponse>, Status> {
let who = self.who(&request);
let asked = request.into_inner();
let repo = (!asked.repo.is_empty()).then_some(asked.repo.as_str());
let allowed = self.decides(&who, &asked.namespace, repo, asked.level);
self.note(format!("Check {}/{} by {who}", asked.namespace, asked.repo));
Ok(Response::new(pb::CheckResponse {
allowed,
reason: if allowed { String::new() } else { format!("principal {who} may not") },
}))
}
async fn add_principal(
&self,
_: Request<pb::AddPrincipalRequest>,
) -> Result<Response<pb::AddPrincipalResponse>, Status> {
Err(unmodelled("AddPrincipal"))
}
async fn list_principals(
&self,
request: Request<pb::ListPrincipalsRequest>,
) -> Result<Response<pb::ListPrincipalsResponse>, Status> {
self.console_only(&request)?;
let principals = self
.principals
.lock()
.expect("principals")
.iter()
.map(|(id, kind)| pb::Principal {
id: id.clone(),
name: id.clone(),
kind: *kind,
created_at_unix_ms: 1,
})
.collect();
Ok(Response::new(pb::ListPrincipalsResponse {
principals,
next_page_token: String::new(),
..Default::default()
}))
}
async fn remove_principal(
&self,
_: Request<pb::RemovePrincipalRequest>,
) -> Result<Response<pb::RemovePrincipalResponse>, Status> {
Err(unmodelled("RemovePrincipal"))
}
async fn rename_principal(
&self,
_: Request<pb::RenamePrincipalRequest>,
) -> Result<Response<pb::RenamePrincipalResponse>, Status> {
Err(unmodelled("RenamePrincipal"))
}
async fn add_key(
&self,
request: Request<pb::AddKeyRequest>,
) -> Result<Response<pb::AddKeyResponse>, Status> {
self.console_only(&request)?;
let asked = request.into_inner();
let parsed = ssh_key::PublicKey::from_openssh(&asked.openssh_public_key)
.map_err(|e| Status::invalid_argument(format!("not an OpenSSH public key: {e}")))?;
let fingerprint = parsed.fingerprint(ssh_key::HashAlg::Sha256).to_string();
self.keys
.lock()
.expect("keys")
.insert(fingerprint.clone(), asked.principal_id.clone());
Ok(Response::new(pb::AddKeyResponse {
key: Some(pb::PublicKey {
fingerprint,
algo: "ssh-ed25519".to_owned(),
principal_id: asked.principal_id,
comment: asked.comment,
added_at_unix_ms: 3,
last_used_unix_ms: None,
expires_at_unix_ms: None,
}),
}))
}
async fn revoke_key(
&self,
_: Request<pb::RevokeKeyRequest>,
) -> Result<Response<pb::RevokeKeyResponse>, Status> {
Err(unmodelled("RevokeKey"))
}
async fn list_keys(
&self,
request: Request<pb::ListKeysRequest>,
) -> Result<Response<pb::ListKeysResponse>, Status> {
let principal = request.into_inner().principal_id;
let keys = self
.keys
.lock()
.expect("keys")
.iter()
.filter(|(_, p)| **p == principal)
.map(|(f, p)| pb::PublicKey {
fingerprint: f.clone(),
algo: "ssh-ed25519".to_owned(),
principal_id: p.clone(),
..Default::default()
})
.collect();
Ok(Response::new(pb::ListKeysResponse { keys }))
}
async fn grant(
&self,
_: Request<pb::GrantRequest>,
) -> Result<Response<pb::GrantResponse>, Status> {
Err(unmodelled("Grant"))
}
async fn revoke(
&self,
_: Request<pb::RevokeRequest>,
) -> Result<Response<pb::RevokeResponse>, Status> {
Err(unmodelled("Revoke"))
}
async fn list_grants(
&self,
request: Request<pb::ListGrantsRequest>,
) -> Result<Response<pb::ListGrantsResponse>, Status> {
self.console_only(&request)?;
let filter = request.into_inner();
let grants = self
.grants
.lock()
.expect("grants")
.iter()
.filter(|g| filter.principal_id.is_empty() || g.principal_id == filter.principal_id)
.filter(|g| filter.namespace.is_empty() || g.namespace == filter.namespace)
.cloned()
.collect();
Ok(Response::new(pb::ListGrantsResponse { grants }))
}
async fn add_group_member(
&self,
_: Request<pb::AddGroupMemberRequest>,
) -> Result<Response<pb::AddGroupMemberResponse>, Status> {
Err(unmodelled("AddGroupMember"))
}
async fn remove_group_member(
&self,
_: Request<pb::RemoveGroupMemberRequest>,
) -> Result<Response<pb::RemoveGroupMemberResponse>, Status> {
Err(unmodelled("RemoveGroupMember"))
}
async fn list_group_members(
&self,
request: Request<pb::ListGroupMembersRequest>,
) -> Result<Response<pb::ListGroupMembersResponse>, Status> {
self.console_only(&request)?;
let group = request.into_inner().group_id;
let members = self
.members
.lock()
.expect("members")
.get(&group)
.cloned()
.unwrap_or_default()
.into_iter()
.map(|id| pb::Principal {
id: id.clone(),
name: id,
kind: pb::PrincipalKind::User as i32,
created_at_unix_ms: 1,
})
.collect();
Ok(Response::new(pb::ListGroupMembersResponse { members }))
}
async fn list_group_membership_events(
&self,
_: Request<pb::ListGroupMembershipEventsRequest>,
) -> Result<Response<pb::ListGroupMembershipEventsResponse>, Status> {
Err(unmodelled("ListGroupMembershipEvents"))
}
}
#[tonic::async_trait]
impl pb::repos_server::Repos for Fake {
// Encrypted repositories (gunnar 2026-10-05): not modelled by this fake.
async fn set_encrypted(
&self,
_: tonic::Request<pb::SetEncryptedRequest>,
) -> Result<tonic::Response<pb::SetEncryptedResponse>, tonic::Status> {
Err(tonic::Status::unimplemented("Repos.SetEncrypted"))
}
async fn recipient_keys(
&self,
_: tonic::Request<pb::RecipientKeysRequest>,
) -> Result<tonic::Response<pb::RecipientKeysResponse>, tonic::Status> {
Err(tonic::Status::unimplemented("Repos.RecipientKeys"))
}
async fn list_namespaces(
&self,
request: Request<pb::ListNamespacesRequest>,
) -> Result<Response<pb::ListNamespacesResponse>, Status> {
let who = self.who(&request);
let mut names: std::collections::BTreeSet<String> = self
.plain
.lock()
.expect("plain")
.iter()
.chain(self.mirrors.lock().expect("mirrors").keys())
.filter_map(|p| p.split_once('/').map(|(ns, _)| ns.to_owned()))
.collect();
names.extend(self.grants.lock().expect("grants").iter().map(|g| g.namespace.clone()));
names.insert(who.clone());
let namespaces = names
.into_iter()
.filter(|ns| !ns.is_empty() && self.decides(&who, ns, None, pb::GrantLevel::Read as i32))
.map(|name| pb::Namespace {
name,
..Default::default()
})
.collect();
Ok(Response::new(pb::ListNamespacesResponse {
namespaces,
next_page_token: String::new(),
}))
}
async fn list(
&self,
request: Request<pb::ListReposRequest>,
) -> Result<Response<pb::ListReposResponse>, Status> {
let who = self.who(&request);
let namespace = request.into_inner().namespace;
let selves = self.selves(&who);
let readable = |ns: &str, repo: &str| {
who == "console"
|| who == ns
|| self.grants.lock().expect("grants").iter().any(|g| {
selves.contains(&g.principal_id)
&& g.namespace == ns
&& (g.repo.is_empty() || g.repo == repo)
})
};
let mut names: Vec<String> = self.plain.lock().expect("plain").clone();
names.extend(self.mirrors.lock().expect("mirrors").keys().cloned());
let repos = names
.into_iter()
.filter_map(|n| {
let (ns, repo) = n.split_once('/')?;
((namespace.is_empty() || ns == namespace) && readable(ns, repo)).then(|| pb::Repo {
path: Some(crate::api::repo_path(ns, repo)),
..Default::default()
})
})
.collect();
Ok(Response::new(pb::ListReposResponse {
repos,
next_page_token: String::new(),
}))
}
async fn get(
&self,
request: Request<pb::GetRepoRequest>,
) -> Result<Response<pb::GetRepoResponse>, Status> {
let path = request.into_inner().path.unwrap_or_default();
let name = key(&path);
let known = self.plain.lock().expect("plain").contains(&name)
|| self.mirrors.lock().expect("mirrors").contains_key(&name);
if !known {
self.refused.lock().expect("refused").push(format!("Repos.Get {name}"));
return Err(Status::permission_denied(format!(
"principal has no read grant on repo:{name}"
)));
}
Ok(Response::new(pb::GetRepoResponse {
repo: Some(pb::Repo {
path: Some(path),
..Default::default()
}),
}))
}
}
#[tonic::async_trait]
impl pb::usage_server::Usage for Fake {
async fn read(
&self,
request: Request<pb::ReadUsageRequest>,
) -> Result<Response<pb::ReadUsageResponse>, Status> {
if *self.usage_down.lock().expect("usage_down") {
return Err(Status::unavailable("the meters are being read"));
}
let namespace = request.get_ref().namespace.clone();
self.require(&request, "Usage.Read", &namespace, None, pb::GrantLevel::Read)?;
let (held, cap) = self
.room
.lock()
.expect("room")
.get(&namespace)
.copied()
.unwrap_or((0, None));
Ok(Response::new(pb::ReadUsageResponse {
usage: Some(pb::NamespaceUsage {
namespace,
pack_bytes: held,
..Default::default()
}),
allowance: Some(pb::Allowance {
pack_bytes: cap,
..Default::default()
}),
// gunnar's own `remaining`: cap − held, absent when uncapped.
remaining: Some(pb::Remaining {
pack_bytes: cap.map(|c| c.saturating_sub(held)),
..Default::default()
}),
repos: Vec::new(),
}))
}
async fn read_all(
&self,
_: Request<pb::ReadAllUsageRequest>,
) -> Result<Response<pb::ReadAllUsageResponse>, Status> {
Err(unmodelled("Usage.ReadAll"))
}
async fn activity(
&self,
_: Request<pb::ActivityRequest>,
) -> Result<Response<pb::ActivityResponse>, Status> {
Err(unmodelled("Usage.Activity"))
}
async fn admit_plan(
&self,
_: Request<pb::AdmitPlanRequest>,
) -> Result<Response<pb::AdmitPlanResponse>, Status> {
Err(unmodelled("Usage.AdmitPlan"))
}
}
// ───────────────────────────────────────────────────────────────────────────
// The console
// ───────────────────────────────────────────────────────────────────────────
struct Rig {
router: axum::Router,
webauthn: Arc<crate::auth::Webauthn>,
fake: Fake,
}
impl Rig {
/// A viewer session for `user`, bound to the principal of the same name.
fn viewer(&self, user: &str) -> String {
self.fake.user(user);
self.webauthn
.plant_bound_credential_for_tests(user, Role::Viewer, user);
self.webauthn.make_session(user, Role::Viewer, true)
}
/// A viewer whose passkey is bound to NO principal.
fn unbound(&self, user: &str) -> String {
self.webauthn.make_session(user, Role::Viewer, true)
}
fn admin(&self, user: &str) -> String {
self.webauthn.make_session(user, Role::Admin, true)
}
async fn send(
&self,
method: &str,
uri: &str,
cookie: &str,
body: Option<serde_json::Value>,
) -> (http::StatusCode, String) {
let request = http::Request::builder()
.method(method)
.uri(uri)
.header(http::header::CONTENT_TYPE, "application/json")
.header(http::header::ORIGIN, "https://console.example")
.header(
http::header::COOKIE,
format!("{}={cookie}", crate::auth::SESSION_COOKIE),
)
.body(axum::body::Body::from(
body.map(|b| b.to_string()).unwrap_or_default(),
))
.expect("a request");
let response = self
.router
.clone()
.oneshot(request)
.await
.expect("the console answers");
let status = response.status();
let bytes = axum::body::to_bytes(response.into_body(), 4 << 20)
.await
.expect("a body");
(status, String::from_utf8_lossy(&bytes).into_owned())
}
}
async fn console(fake: Fake, limits: Limits) -> Rig {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("a loopback port");
let addr = listener.local_addr().expect("its address");
let serving = fake.clone();
tokio::spawn(async move {
tonic::transport::Server::builder()
.add_service(pb::mirror_server::MirrorServer::new(serving.clone()))
.add_service(pb::access_server::AccessServer::new(serving.clone()))
.add_service(pb::repos_server::ReposServer::new(serving.clone()))
.add_service(pb::usage_server::UsageServer::new(serving))
.serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
.await
});
let dir = tempfile::tempdir().expect("a temp dir").keep();
let store = crate::state::StateStore::open(dir.join("state.redb")).expect("a store");
let webauthn = Arc::new(
crate::auth::Webauthn::new(
crate::auth::RelyingParty {
rp_id: "console.example".to_owned(),
origin: "https://console.example".to_owned(),
rp_name: "gunnar".to_owned(),
},
store.clone(),
)
.expect("a subsystem"),
);
let endpoint = format!("http://{addr}");
let router = crate::serve::router(Arc::new(crate::app::App {
webauthn: Some(webauthn.clone()),
admin_token: Some("break-glass".to_owned()),
#[cfg(feature = "metered")]
monetize: None,
events: crate::sse::EventHub::detached(),
gunnar: crate::grpc::Gunnar::connect_lazy(&endpoint, Arc::new(console_signer()))
.expect("a handle"),
twin: None,
guest: None,
reviewers: Arc::new(crate::reviewer::ReviewerKeys::new(
endpoint.clone(),
store.clone(),
)),
tenants: Arc::new(crate::tenant::TenantKeys::new(endpoint, store)),
forges: Default::default(),
imports: Arc::new(Imports::with_limits(limits)),
// The mock forge is on loopback, which the SSRF guard refuses; the
// harness turns it off the one way a deployment can.
forge_apps: Arc::new(ForgeApps::allowing_private_bases().switched_on()),
ui_dir: None,
caches: crate::api::Caches::default(),
objects: Default::default(),
readmes: Default::default(),
}));
Rig {
router,
webauthn,
fake,
}
}
fn roomy() -> Limits {
Limits {
concurrent: 50,
syncs_per_hour: 500,
}
}
fn json(body: &str) -> serde_json::Value {
serde_json::from_str(body).unwrap_or_else(|e| panic!("not JSON: {e}\n{body}"))
}
fn by_url(namespace: &str, url: &str) -> serde_json::Value {
serde_json::json!({ "url": url, "namespace": namespace })
}
// ───────────────────────────────────────────────────────────────────────────
// 1. Authorization
// ───────────────────────────────────────────────────────────────────────────
/// ★ **A viewer imports into their own namespace.** Red before 2026-10-02:
/// `/api/mirrors` was on `role_floor`'s admin list and the URL door did not
/// exist.
#[tokio::test]
async fn a_viewer_imports_a_repository_by_url_into_their_own_namespace() {
let b = console(Fake::default(), roomy()).await;
let kim = b.viewer("kim");
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(by_url("kim", "https://code.example.org/upstream/tool.git")),
)
.await;
assert_eq!(status, http::StatusCode::OK, "{body}");
let answer = json(&body);
assert_eq!(answer["queued"], 1, "{body}");
assert_eq!(answer["results"][0]["path"], "kim/tool", "{body}");
let mutations: Vec<String> = b
.fake
.heard()
.into_iter()
.filter(|c| !c.starts_with("Status"))
.collect();
assert_eq!(
mutations,
vec![
"Add kim/tool <- https://code.example.org/upstream/tool.git".to_owned(),
"Sync kim/tool".to_owned(),
],
"gunnar must hear exactly the registration and the first sync"
);
// ★ Signed AS kim (thin layer, owner 2026-10-04): gunnar decided, on her
// own key — never the console's.
assert_eq!(
[b.fake.signed_for("Mirror.Add"), b.fake.signed_for("Mirror.Sync")].concat(),
vec!["Mirror.Add by kim".to_owned(), "Mirror.Sync by kim".to_owned()],
);
}
/// ★ **GUN-MIRROR-CA: an import from an address behind a private CA** — the CA pasted
/// beside the URL is handed to gunnar (`Mirror.SetCa`, as the person) between the
/// registration and the first sync, verbatim; the console judges nothing about it.
#[tokio::test]
async fn an_import_by_url_hands_its_private_ca_to_gunnar_before_the_first_sync() {
let b = console(Fake::default(), roomy()).await;
let kim = b.viewer("kim");
let mut ask = by_url("kim", "https://git.acme.internal/upstream/tool.git");
ask["ca_pem"] = serde_json::json!("-----BEGIN CERTIFICATE-----\nMIIB\n-----END CERTIFICATE-----\n");
let (status, body) = b.send("POST", "/api/mirrors/import-url", &kim, Some(ask)).await;
assert_eq!(status, http::StatusCode::OK, "{body}");
let mutations: Vec<String> = b
.fake
.heard()
.into_iter()
.filter(|c| !c.starts_with("Status"))
.collect();
assert_eq!(
mutations,
vec![
"Add kim/tool <- https://git.acme.internal/upstream/tool.git".to_owned(),
"SetCa kim/tool -----BEGIN CERTIFICATE-----".to_owned(),
"Sync kim/tool".to_owned(),
],
"the CA goes to gunnar between the registration and the first sync"
);
assert_eq!(b.fake.signed_for("Mirror.SetCa"), vec!["Mirror.SetCa by kim".to_owned()]);
}
/// ★ **gunnar's rule, not the console's looser copy**: a namespace the person
/// holds `admin` over — directly, or through a group — is theirs to import
/// into; `write` alone is NOT (`Mirror.Add` creating a repository is Admin over
/// the namespace at gunnar). Red before the thin layer: the console's own
/// grant engine let a write holder add, re-point and remove mirrors, and signed
/// it with its server-wide key.
#[tokio::test]
async fn admin_over_a_team_namespace_imports_there_a_group_grant_counts_and_write_alone_does_not() {
let fake = Fake::default();
fake.grant("kim", "team", "", pb::GrantLevel::Admin);
fake.group("devs", &["kim"]);
fake.grant("devs", "devspace", "", pb::GrantLevel::Admin);
fake.grant("kim", "pushonly", "", pb::GrantLevel::Write);
let b = console(fake, roomy()).await;
let kim = b.viewer("kim");
for ns in ["team", "devspace"] {
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(by_url(ns, "https://code.example.org/upstream/tool.git")),
)
.await;
assert_eq!(status, http::StatusCode::OK, "{ns}: {body}");
assert!(
b.fake.heard_any(&format!("Add {ns}/tool")),
"{ns}: {:?}",
b.fake.heard()
);
}
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(by_url("pushonly", "https://code.example.org/upstream/tool.git")),
)
.await;
assert_eq!(status, http::StatusCode::FORBIDDEN, "write alone imported: {body}");
assert!(json(&body)["message"].as_str().unwrap_or_default().contains("pushonly"), "{body}");
assert!(!b.fake.heard_any("Add pushonly"), "{:?}", b.fake.heard());
assert!(
b.fake.refused_any("Mirror.Add namespace:pushonly by kim"),
"the refusal is gunnar's, of kim's own call"
);
}
/// ★ **Into somebody else's namespace: refused BY GUNNAR, on the person's own
/// key, and nothing is registered.**
#[tokio::test]
async fn an_import_into_somebody_elses_namespace_is_refused_by_gunnar_and_reaches_no_mirror() {
let fake = Fake::default();
fake.user("alice");
let b = console(fake, roomy()).await;
let kim = b.viewer("kim-intruder");
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(by_url(
"alice",
"https://code.example.org/upstream/tool.git",
)),
)
.await;
assert_eq!(status, http::StatusCode::FORBIDDEN, "{body}");
let fail = json(&body);
assert_eq!(fail["code"], "permissiondenied", "{body}");
let words = fail["message"].as_str().unwrap_or_default();
assert!(words.contains("alice"), "{words}");
assert!(
!b.fake.heard_any("Add") && !b.fake.heard_any("Sync"),
"a refused import reached the mirror service: {:?}",
b.fake.heard()
);
assert!(
b.fake
.refused
.lock()
.expect("refused")
.iter()
.all(|r| r.ends_with("by kim-intruder")),
"gunnar refused somebody other than the person who asked"
);
}
/// ★ **A reader of a namespace is not an importer into it**, and a session
/// bound to no principal has no namespace at all.
#[tokio::test]
async fn a_reader_is_refused_and_so_is_a_session_bound_to_nobody() {
let fake = Fake::default();
fake.grant("vera", "docs", "", pb::GrantLevel::Read);
let b = console(fake, roomy()).await;
let vera = b.viewer("vera");
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&vera,
Some(by_url("docs", "https://code.example.org/upstream/tool.git")),
)
.await;
assert_eq!(status, http::StatusCode::FORBIDDEN, "{body}");
assert_eq!(json(&body)["code"], "permissiondenied", "{body}");
assert!(b.fake.refused_any("Mirror.Add namespace:docs by vera"), "gunnar's refusal");
let nobody = b.unbound("nobody");
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&nobody,
Some(by_url(
"nobody",
"https://code.example.org/upstream/tool.git",
)),
)
.await;
assert_eq!(status, http::StatusCode::FORBIDDEN, "{body}");
assert!(json(&body)["message"]
.as_str()
.unwrap_or_default()
.contains("log in again"));
assert!(!b.fake.heard_any("Add"), "{:?}", b.fake.heard());
}
/// An administrator keeps today's full, box-wide power: any namespace, and the
/// whole box in the list.
#[tokio::test]
async fn an_admin_imports_anywhere_and_sees_every_mirror_while_a_viewer_sees_their_own() {
let fake = Fake::default();
fake.already("alice/private-thing", "https://code.example.org/a/p.git");
let b = console(fake, roomy()).await;
let ada = b.admin("ada");
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&ada,
Some(by_url("bob", "https://code.example.org/upstream/tool.git")),
)
.await;
assert_eq!(status, http::StatusCode::OK, "{body}");
let kim = b.viewer("kim");
let (status, _) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(by_url("kim", "https://code.example.org/upstream/mine.git")),
)
.await;
assert_eq!(status, http::StatusCode::OK);
let (_, all) = b.send("GET", "/api/mirrors", &ada, None).await;
for path in ["alice/private-thing", "bob/tool", "kim/mine"] {
assert!(all.contains(path), "the admin's list lacks {path}: {all}");
}
let (status, own) = b.send("GET", "/api/mirrors", &kim, None).await;
assert_eq!(status, http::StatusCode::OK, "{own}");
assert!(own.contains("kim/mine"), "{own}");
assert!(
!own.contains("alice/private-thing") && !own.contains("bob/tool"),
"a viewer was shown mirrors that are not theirs: {own}"
);
}
/// Sync, forget and stop on a mirror follow gunnar's rule: one's own, yes;
/// somebody else's, refused BY GUNNAR on the person's own key — nothing
/// changes.
#[tokio::test]
async fn a_viewer_tends_their_own_mirror_and_not_somebody_elses() {
let fake = Fake::default();
fake.already("kim/mine", "https://code.example.org/k/m.git");
fake.already("alice/theirs", "https://code.example.org/a/t.git");
let b = console(fake, roomy()).await;
let kim = b.viewer("kim");
let (status, body) = b
.send("POST", "/api/mirrors/kim/mine/sync", &kim, None)
.await;
assert_eq!(status, http::StatusCode::OK, "{body}");
assert!(b.fake.heard_any("Sync kim/mine"));
let (status, body) = b
.send("POST", "/api/mirrors/alice/theirs/sync", &kim, None)
.await;
assert_eq!(status, http::StatusCode::FORBIDDEN, "{body}");
let (status, _) = b
.send(
"POST",
"/api/mirrors/alice/theirs/credential",
&kim,
Some(serde_json::json!({"kind": "none"})),
)
.await;
assert_eq!(status, http::StatusCode::FORBIDDEN);
let (status, _) = b
.send(
"DELETE",
"/api/mirrors/alice/theirs?confirm=alice/theirs",
&kim,
None,
)
.await;
assert_eq!(status, http::StatusCode::FORBIDDEN);
assert!(
!b.fake.heard_any("Sync alice") && !b.fake.heard_any("Remove alice"),
"{:?}",
b.fake.heard()
);
}
/// The status panel on a repository page is a reader's: the tenant's OWN
/// credential asks, so gunnar decides — and a repository they may not read is
/// answered as "no mirror", the same answer as a repository with none.
#[tokio::test]
async fn a_viewer_reads_the_status_of_a_mirror_they_may_read_and_nothing_of_one_they_may_not() {
let fake = Fake::default();
fake.already("kim/mine", "https://code.example.org/k/m.git");
fake.already("alice/theirs", "https://code.example.org/a/t.git");
let b = console(fake, roomy()).await;
let kim = b.viewer("kim");
let (status, body) = b.send("GET", "/api/mirrors/kim/mine", &kim, None).await;
assert_eq!(status, http::StatusCode::OK, "{body}");
assert_eq!(json(&body)["path"], "kim/mine", "{body}");
assert!(
b.fake.heard_any("Status kim/mine by kim"),
"{:?}",
b.fake.heard()
);
let (status, body) = b.send("GET", "/api/mirrors/alice/theirs", &kim, None).await;
assert_eq!(status, http::StatusCode::OK, "{body}");
assert_eq!(body.trim(), "null", "{body}");
}
// ───────────────────────────────────────────────────────────────────────────
// 2. Room
// ───────────────────────────────────────────────────────────────────────────
/// ★ **A full namespace imports nothing, in customer words.**
#[tokio::test]
async fn an_import_into_a_full_namespace_needs_more_room_and_reaches_no_mirror() {
let fake = Fake::default();
fake.room("kim", 1 << 30, Some(1 << 30));
let b = console(fake, roomy()).await;
let kim = b.viewer("kim");
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(by_url("kim", "https://code.example.org/upstream/tool.git")),
)
.await;
assert_eq!(status, http::StatusCode::FORBIDDEN, "{body}");
let fail = json(&body);
assert_eq!(fail["code"], "needsroom", "{body}");
assert!(
fail["message"]
.as_str()
.unwrap_or_default()
.contains("needs more room"),
"{body}"
);
assert!(!b.fake.heard_any("Add"), "{:?}", b.fake.heard());
// A sync into it is refused the same way: the room is re-checked on every
// sync, not only on the first.
b.fake
.already("kim/old", "https://code.example.org/k/o.git");
let (status, body) = b
.send("POST", "/api/mirrors/kim/old/sync", &kim, None)
.await;
assert_eq!(status, http::StatusCode::FORBIDDEN, "{body}");
assert_eq!(json(&body)["code"], "needsroom", "{body}");
assert!(!b.fake.heard_any("Sync"), "{:?}", b.fake.heard());
}
/// ★ **A forge repository whose stated size does not fit is refused up front**,
/// row by row, and the one that does fit goes ahead. The estimates of one
/// import add up: two that each fit alone do not both fit together.
#[tokio::test]
async fn a_forge_repository_bigger_than_the_room_left_needs_more_room_and_the_rest_go_ahead() {
let forge = MockServer::start();
let repo = |name: &str, size_kib: u64| {
serde_json::json!({
"name": name,
"full_name": format!("kim-gh/{name}"),
"clone_url": format!("https://github.com/kim-gh/{name}.git"),
"ssh_url": format!("git@github.com:kim-gh/{name}.git"),
"default_branch": "main",
"private": false,
"archived": false,
"size": size_kib,
"description": serde_json::Value::Null,
})
};
forge.on_body(
"GET",
"/user/repos",
200,
&serde_json::json!([
repo("small", 300 * 1024),
repo("also-small", 300 * 1024),
repo("huge", 4 * 1024 * 1024)
])
.to_string(),
);
let fake = Fake::default();
// 1 GiB cap, 500 MiB held: 524 MiB left.
fake.room("kim", 500 << 20, Some(1 << 30));
let b = console(fake, roomy()).await;
let kim = b.viewer("kim");
let (status, body) = b
.send(
"POST",
"/api/forges/connect",
&kim,
Some(serde_json::json!({"forge": "github", "base": forge.url(), "token": TOKEN})),
)
.await;
assert_eq!(
status,
http::StatusCode::OK,
"a viewer connects their own forge: {body}"
);
let (status, body) = b
.send(
"POST",
"/api/mirrors/import",
&kim,
Some(serde_json::json!({
"forge": "github",
"namespace": "kim",
"transport": "ssh",
"repos": ["kim-gh/small", "kim-gh/also-small", "kim-gh/huge"],
})),
)
.await;
assert_eq!(status, http::StatusCode::OK, "{body}");
let answer = json(&body);
let rows = answer["results"].as_array().expect("rows").clone();
assert_eq!(rows[0]["ok"], true, "{body}");
assert_eq!(
rows[1]["ok"], false,
"the second 300 MiB no longer fits: {body}"
);
assert_eq!(rows[1]["step"], "room", "{body}");
assert!(rows[1]["message"]
.as_str()
.unwrap_or_default()
.contains("needs more room"));
assert_eq!(rows[2]["step"], "room", "{body}");
#[cfg(feature = "metered")]
assert_eq!(rows[2]["room_href"], "/account", "{body}");
assert!(b.fake.heard_any("Add kim/small"));
assert!(!b.fake.heard_any("Add kim/also-small") && !b.fake.heard_any("Add kim/huge"));
}
/// When the appliance cannot say how much room is left, a customer's import is
/// not guessed at: it is refused with a sentence that says to try again.
#[tokio::test]
async fn an_unanswered_room_question_refuses_a_viewers_import_in_plain_words() {
let fake = Fake::default();
*fake.usage_down.lock().expect("usage_down") = true;
let b = console(fake, roomy()).await;
let kim = b.viewer("kim");
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(by_url("kim", "https://code.example.org/upstream/tool.git")),
)
.await;
assert!(!status.is_success(), "{status} {body}");
assert!(
json(&body)["message"]
.as_str()
.unwrap_or_default()
.contains("could not tell how much room"),
"{body}"
);
assert!(!b.fake.heard_any("Add"));
}
/// A job gunnar stopped at the cap is shown in customer words, with the way to
/// more room.
#[tokio::test]
async fn a_sync_stopped_at_the_cap_reads_as_needs_more_room() {
let fake = Fake::default();
fake.already("kim/big", "https://code.example.org/k/b.git");
let b = console(fake, roomy()).await;
let kim = b.viewer("kim");
let (status, _) = b
.send("POST", "/api/mirrors/kim/big/sync", &kim, None)
.await;
assert_eq!(status, http::StatusCode::OK);
b.fake.finish_because(
"kim/big",
pb::MirrorJobState::Failed,
// Worded so no fragment of it could be matched: the console reads
// gunnar's FIELD (thin layer, 2026-10-04), never its prose.
"fetching kim/big from https://code.example.org/k/b.git: stopped at the namespace's \
ceiling, nothing was stored",
pb::MirrorRefusal::PackCap,
);
let (_, body) = b.send("GET", "/api/mirrors/kim/big", &kim, None).await;
let job = &json(&body)["job"];
assert_eq!(job["state"], "failed", "{body}");
assert_eq!(job["needs_room"], true, "{body}");
assert!(
job["words"]
.as_str()
.unwrap_or_default()
.contains("needs more room"),
"{body}"
);
}
// ───────────────────────────────────────────────────────────────────────────
// 3. Limits
// ───────────────────────────────────────────────────────────────────────────
/// ★ **At most N at once per person**, and a slot frees when gunnar says the
/// job settled.
#[tokio::test]
async fn at_most_two_imports_at_once_per_person_and_a_finished_one_frees_its_slot() {
let b = console(
Fake::default(),
Limits {
concurrent: 2,
syncs_per_hour: 100,
},
)
.await;
let kim = b.viewer("kim");
for name in ["a", "b"] {
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(by_url(
"kim",
&format!("https://code.example.org/u/{name}.git"),
)),
)
.await;
assert_eq!(status, http::StatusCode::OK, "{name}: {body}");
}
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(by_url("kim", "https://code.example.org/u/c.git")),
)
.await;
assert_eq!(status, http::StatusCode::TOO_MANY_REQUESTS, "{body}");
assert_eq!(json(&body)["code"], "busy", "{body}");
assert!(!b.fake.heard_any("Add kim/c"), "{:?}", b.fake.heard());
// Somebody else is not held to kim's count.
let bo = b.viewer("bo");
let (status, _) = b
.send(
"POST",
"/api/mirrors/import-url",
&bo,
Some(by_url("bo", "https://code.example.org/u/x.git")),
)
.await;
assert_eq!(status, http::StatusCode::OK);
b.fake.finish("kim/a", pb::MirrorJobState::Done, "");
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(by_url("kim", "https://code.example.org/u/c.git")),
)
.await;
assert_eq!(
status,
http::StatusCode::OK,
"a settled job frees its slot: {body}"
);
}
/// ★ **Sync is rate-limited per person**, and the refusal says when.
#[tokio::test]
async fn syncs_are_rate_limited_per_person_and_an_admin_is_not() {
let fake = Fake::default();
fake.already("kim/mine", "https://code.example.org/k/m.git");
let b = console(
fake,
Limits {
concurrent: 100,
syncs_per_hour: 2,
},
)
.await;
let kim = b.viewer("kim");
for _ in 0..2 {
let (status, body) = b
.send("POST", "/api/mirrors/kim/mine/sync", &kim, None)
.await;
assert_eq!(status, http::StatusCode::OK, "{body}");
b.fake.finish("kim/mine", pb::MirrorJobState::Done, "");
}
let (status, body) = b
.send("POST", "/api/mirrors/kim/mine/sync", &kim, None)
.await;
assert_eq!(status, http::StatusCode::TOO_MANY_REQUESTS, "{body}");
assert!(
json(&body)["message"]
.as_str()
.unwrap_or_default()
.contains("in the last hour"),
"{body}"
);
let syncs = b
.fake
.heard()
.iter()
.filter(|c| c.starts_with("Sync"))
.count();
assert_eq!(syncs, 2, "the third press reached gunnar");
let ada = b.admin("ada");
for _ in 0..3 {
let (status, body) = b
.send("POST", "/api/mirrors/kim/mine/sync", &ada, None)
.await;
assert_eq!(status, http::StatusCode::OK, "{body}");
b.fake.finish("kim/mine", pb::MirrorJobState::Done, "");
}
}
// ───────────────────────────────────────────────────────────────────────────
// 4. The credential goes one way
// ───────────────────────────────────────────────────────────────────────────
/// ★ **The token reaches gunnar once and nothing brings it back** — not the
/// answer, not the status, not the list, not a log line.
#[tokio::test]
async fn a_token_given_with_a_url_reaches_gunnar_once_and_is_never_shown_again() {
crate::captured::install();
let b = console(Fake::default(), roomy()).await;
let kim = b.viewer("kim-token");
let mut bodies = Vec::new();
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(serde_json::json!({
"url": "https://github.com/kim/private.git",
"namespace": "kim-token",
"token": TOKEN,
})),
)
.await;
assert_eq!(status, http::StatusCode::OK, "{body}");
bodies.push(body);
assert_eq!(
b.fake.secrets.lock().expect("secrets").clone(),
vec![TOKEN.to_owned()],
"gunnar must be handed the token exactly once — the positive half of the proof"
);
assert!(
b.fake
.heard_any("SetCredential kim-token/private x-access-token"),
"a github token is paired with its documented user name: {:?}",
b.fake.heard()
);
for uri in ["/api/mirrors", "/api/mirrors/kim-token/private"] {
let (_, body) = b.send("GET", uri, &kim, None).await;
bodies.push(body);
}
for body in &bodies {
assert!(!body.contains(TOKEN), "a body carried the token: {body}");
}
let log = crate::captured::written();
assert!(!log.contains(TOKEN), "a log line carried the token");
// A token over ssh is a mismatch, refused before anything is registered.
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(serde_json::json!({
"url": "ssh://git@github.com/kim/other.git",
"namespace": "kim-token",
"token": TOKEN,
})),
)
.await;
assert_eq!(status, http::StatusCode::BAD_REQUEST, "{body}");
assert!(!b.fake.heard_any("Add kim-token/other"));
}
/// The panel's buttons follow the server's word: a mirror the viewer may
/// READ but not tend comes back with `may_tend: false`, one of their own with
/// `true`, and every row of the list is one they tend.
#[tokio::test]
async fn the_answer_says_whether_this_session_may_tend_the_mirror() {
let fake = Fake::default();
fake.already("kim/mine", "https://code.example.org/k/m.git");
fake.already("alice/shared", "https://code.example.org/a/s.git");
fake.grant("kim", "alice", "shared", pb::GrantLevel::Read);
let b = console(fake, roomy()).await;
let kim = b.viewer("kim");
let (_, body) = b.send("GET", "/api/mirrors/kim/mine", &kim, None).await;
assert_eq!(json(&body)["may_tend"], true, "{body}");
let (_, body) = b.send("GET", "/api/mirrors/alice/shared", &kim, None).await;
assert_eq!(
json(&body)["path"],
"alice/shared",
"a reader sees it: {body}"
);
assert_eq!(
json(&body)["may_tend"],
false,
"a reader does not tend it: {body}"
);
let (_, body) = b.send("GET", "/api/mirrors", &kim, None).await;
let rows = json(&body);
let rows = rows.as_array().expect("a list");
assert_eq!(rows.len(), 1, "{body}");
assert_eq!(rows[0]["may_tend"], true, "{body}");
// ★ gunnar's answer, asked as kim (thin layer, owner 2026-10-04).
assert!(b.fake.heard_any("Check alice/shared by kim"), "{:?}", b.fake.heard());
assert!(b.fake.heard_any("Check kim/mine by kim"), "{:?}", b.fake.heard());
}
/// ★ **An import never pulls over a repository that is not a mirror — and
/// it is gunnar that refuses** (`Mirror.Add` without `adopt`, gunnar 739b5f1).
/// Without it a person who may import into a team namespace could turn a
/// colleague's ordinary repository into a copy of some remote by typing its
/// name. Re-importing one's own mirror is the picker's ordinary "press it
/// twice"; taking a repository over is the control plane's `--adopt`, never
/// the console's — not for an administrator either.
#[tokio::test]
async fn an_import_never_pulls_over_a_repository_that_is_not_a_mirror() {
let fake = Fake::default();
// Admin over the namespace: what gunnar's `Mirror.Add` asks of an importer.
fake.grant("kim", "team", "", pb::GrantLevel::Admin);
fake.plain
.lock()
.expect("plain")
.push("team/notes".to_owned());
fake.already("team/mirrored", "https://code.example.org/t/m.git");
let b = console(fake, roomy()).await;
let kim = b.viewer("kim");
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(serde_json::json!({
"url": "https://code.example.org/upstream/tool.git",
"namespace": "team",
"name": "notes",
})),
)
.await;
// gunnar's refusal of the person's own `Mirror.Add` (thin layer, owner
// 2026-10-04): the console never adopts, and gunnar names the repository.
assert_eq!(status, http::StatusCode::BAD_REQUEST, "{body}");
assert!(
json(&body)["message"]
.as_str()
.unwrap_or_default()
.contains("is not a mirror"),
"{body}"
);
assert!(!b.fake.heard_any("Add team/notes"), "{:?}", b.fake.heard());
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(serde_json::json!({
"url": "https://code.example.org/t/m.git",
"namespace": "team",
"name": "mirrored",
})),
)
.await;
assert_eq!(
status,
http::StatusCode::OK,
"re-importing a mirror is fine: {body}"
);
let ada = b.admin("ada");
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&ada,
Some(serde_json::json!({
"url": "https://code.example.org/upstream/tool.git",
"namespace": "team",
"name": "notes",
})),
)
.await;
// The console never takes a repository over, not for an administrator
// either: that is `gunnar control mirror add --create --adopt`, a decision
// made at the control plane with the operator's own key.
assert_eq!(
status,
http::StatusCode::BAD_REQUEST,
"the console adopted a repository: {body}"
);
}
/// ★ **The ordinary import path asks gunnar nothing it would refuse.** A
/// one-repository read of a repository that does not exist yet is answered
/// `PermissionDenied` by gunnar — which does not say "absent" — and each one is
/// a WARN line in main's log: two per import, on the path every customer
/// takes. Measured in the uitest's server log, 2026-10-02.
#[tokio::test]
async fn an_ordinary_import_makes_no_call_gunnar_would_refuse() {
let b = console(Fake::default(), roomy()).await;
let kim = b.viewer("kim");
let (status, body) = b
.send(
"POST",
"/api/mirrors/import-url",
&kim,
Some(by_url("kim", "https://code.example.org/upstream/fresh.git")),
)
.await;
assert_eq!(status, http::StatusCode::OK, "{body}");
assert_eq!(
b.fake.refused.lock().expect("refused").clone(),
Vec::<String>::new(),
"the import made calls gunnar refuses (a WARN line each on main)"
);
}