crates/gunnar-server/tests/login_during_grow.rs
//! **A passkey login while the data set is being grown** (grow-login-503, 2026-10-06):
//! over the REAL plane and stores, a login through the identity core (IDENTITY-HOST: the
//! console relays `IdentityService.LoginFinish`) while the transaction log is RELEASED for
//! the remount. MEASURED on cov-E's round-3 KVM mockup: the kit's login met `HTTP 401:
//! internal error: the control store failed: transaction log: txlog: released — … writes
//! resume after the remount` — a transient condition answered as a wrong credential. The
//! core answers it as a STORE refusal (`nornir-identity-refusal: store`, never `ceremony`),
//! saying the writes resume after the remount, which the console reads as "try again"; once
//! the log is taken back the same passkey logs in.
use std::sync::Arc;
use gunnar_grpc::{ControlPlane, ServerConfig};
use gunnar_proto::*;
use gunnar_server::control_plane::CoreControlPlane;
use gunnar_server::stores::ServerStores;
use nornir_identity::test_host::SoftPasskey;
use nornir_identity::{Code, Relay};
const RP_ID: &str = "console.example";
const ORIGIN: &str = "https://console.example";
fn login(
identity: &gunnar_grpc::identity::GunnarIdentity,
key: &mut SoftPasskey,
) -> Result<nornir_identity::LoggedIn, nornir_identity::Refused> {
let c = identity.login_start(&Relay {
rp_id: RP_ID.into(),
origin: ORIGIN.into(),
client: None,
})?;
identity.login_finish(key.login(&c, ORIGIN, true))
}
#[tokio::test]
async fn a_login_while_the_log_is_released_for_the_remount_is_unavailable_and_succeeds_after() {
let dir = tempfile::tempdir().expect("tempdir");
let stores = Arc::new(ServerStores::open(dir.path()).expect("stores"));
let plane = Arc::new(CoreControlPlane::new(Arc::clone(&stores)));
let control: Arc<dyn ControlPlane> = plane.clone();
plane
.add_principal("kit-claude", "kit-claude", PrincipalKind::User)
.expect("the person");
let mut key = SoftPasskey::new(21);
plane
.add_credential(
"kit-claude",
&key.id,
&key.public_key(),
"the kit's passkey",
0,
)
.expect("the passkey");
let identity =
gunnar_server::identity::build(dir.path(), control, &ServerConfig::default(), false);
// The growth gave up the log's files for the remount.
stores.txlog().release().expect("released");
let during =
login(&identity, &mut key).expect_err("no login is WRITTEN while the log is released");
assert_eq!(
during.code,
Code::Store,
"a transient remount answered as {:?}: {}",
during.code,
during.detail
);
assert!(during.detail.contains("remount"), "{}", during.detail);
assert!(
!during.detail.contains("internal error"),
"{}",
during.detail
);
// Taken back after the remount: the same passkey logs in.
stores.txlog().reacquire().expect("reacquired");
let after = login(&identity, &mut key).expect("after the remount the login is written");
assert_eq!(after.session.user_id, "kit-claude");
}