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

nordisk/gunnar

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