use std::error::Error;
use std::sync::Arc;
use liminal::durability::bridge::block_on;
use liminal::durability::{DurableStore, open_ephemeral};
use liminal_protocol::wire::{
ClientRequest, ConnectionIncarnation, EnrollmentRequest, EnrollmentToken, ServerValue,
};
use crate::server::mount::MountKind;
use crate::server::participant::{
ConnectionFateClass, ConnectionFateWorkItem, ParticipantConnectionContext,
ParticipantConnectionConversations, ParticipantSemanticError, ParticipantSemanticHandler,
};
use super::ProductionParticipantHandler;
use super::boot_drain::BootDrainVerdict;
use super::handler::LogAppender;
use super::log::{
DecodedStoredOperation, OperationLog, OperationLogError, StoredOperation,
StoredTerminalDisposition,
};
use super::state::{ConversationAuthority, DurableAppend};
use super::tests::test_participant_config;
use super::tests_w1b_pending_died_restart::pending_restart_fixture;
const SEAL_OPEN_SEQUENCE: u64 = 409;
fn seal_config() -> crate::config::types::ParticipantConfig {
let mut config = test_participant_config();
config.max_retained_record_rows = 4;
config
}
struct StoreAppender<'a> {
log: &'a OperationLog,
}
impl DurableAppend for StoreAppender<'_> {
fn append(
&self,
operation: &StoredOperation,
expected_sequence: u64,
) -> Result<(), OperationLogError> {
block_on(self.log.append(operation, expected_sequence))?
}
}
struct LastIdentityStore {
store: Arc<dyn DurableStore>,
conversation_id: u64,
survivor_connection: ConnectionIncarnation,
survivor_participant_id: u64,
minter: ProductionParticipantHandler,
}
impl LastIdentityStore {
fn log(&self) -> OperationLog {
OperationLog::new(Arc::clone(&self.store), self.conversation_id)
}
}
fn last_identity_store() -> Result<LastIdentityStore, Box<dyn Error>> {
let fixture = pending_restart_fixture()?;
let store = Arc::clone(&fixture.handler.store);
let conversation_id = fixture.conversation_id;
let survivor_connection = fixture.peer_connection;
let survivor_participant_id = fixture.peer_participant_id;
drop(fixture);
let minter = ProductionParticipantHandler::new(Arc::clone(&store), seal_config())?;
let log = OperationLog::new(Arc::clone(&store), conversation_id);
let survivors = token_count(&minter, conversation_id)?;
if survivors != 1 {
return Err(format!(
"the last-identity fixture wanted exactly one surviving token after boot #1, \
found {survivors} — the shape it exists to mint is gone"
)
.into());
}
let pending_sequence = {
let cell = minter.cell(conversation_id)?;
let mut owner = cell
.lock()
.map_err(|_| "last-identity owner lock was poisoned")?;
let authority = owner
.as_mut()
.ok_or("last-identity owner was unavailable")?;
let sequence = authority.next_log_sequence;
authority
.prepare_connection_fate_transaction(&ConnectionFateWorkItem {
open_sequence: SEAL_OPEN_SEQUENCE,
connection_incarnation: survivor_connection,
class: ConnectionFateClass::ConnectionLost,
tracked_conversations: Vec::new(),
})
.complete(authority, &StoreAppender { log: &log })?;
drop(owner);
sequence
};
let Some(entry) = block_on(log.read_at(pending_sequence))?? else {
return Err("the survivor's connection fate appended no row".into());
};
let DecodedStoredOperation::V3(StoredOperation::Died { row }) = entry.operation else {
return Err("the survivor's connection fate did not append a Died row".into());
};
if row.participant_id != survivor_participant_id {
return Err("the survivor's Died row names another participant".into());
}
if row.disposition != StoredTerminalDisposition::Pending {
return Err(format!(
"the survivor's terminal committed instead of pending ({:?}) — the \
last-identity shape is no longer mintable this way",
row.disposition
)
.into());
}
Ok(LastIdentityStore {
store,
conversation_id,
survivor_connection,
survivor_participant_id,
minter,
})
}
fn token_count(
handler: &ProductionParticipantHandler,
conversation_id: u64,
) -> Result<usize, Box<dyn Error>> {
let cell = handler.cell(conversation_id)?;
let owner = cell
.lock()
.map_err(|_| "token census owner lock was poisoned")?;
let count = owner
.as_ref()
.ok_or("token census owner was absent")?
.tokens
.len();
drop(owner);
Ok(count)
}
#[test]
fn boot_draining_the_last_identity_seals_the_conversation() -> Result<(), Box<dyn Error>> {
let minted = last_identity_store()?;
let booted = ProductionParticipantHandler::new(Arc::clone(&minted.store), seal_config())?;
let cell = booted.cell(minted.conversation_id)?;
let owner = cell.lock().map_err(|_| "sealed owner lock was poisoned")?;
let authority = owner.as_ref().ok_or("sealed owner was absent")?;
if !authority.is_closed() {
return Err("the last-identity drain left the conversation unsealed".into());
}
if !authority.tokens.is_empty() {
return Err("the sealed conversation retained enrollment tokens".into());
}
if authority.frontier().is_some() {
return Err("the sealed conversation retained an executable frontier".into());
}
if authority
.slots
.contains_key(&minted.survivor_participant_id)
{
return Err("the sealed conversation retained the drained identity's slot".into());
}
drop(owner);
let replayed = booted.replay_aggregate_reference(minted.conversation_id, &minted.log())?;
if !replayed.is_closed() || replayed.frontier().is_some() || !replayed.tokens.is_empty() {
return Err("replaying the sealed bytes did not rebuild the closure".into());
}
let consumer: &dyn ParticipantSemanticHandler = &booted;
consumer
.handle_connection_fate(ConnectionFateWorkItem {
open_sequence: SEAL_OPEN_SEQUENCE,
connection_incarnation: minted.survivor_connection,
class: ConnectionFateClass::ConnectionLost,
tracked_conversations: vec![minted.conversation_id],
})
.map_err(|error| {
format!("the retained Open failed before Complete at the recovery consumer: {error}")
})?;
Ok(())
}
#[test]
fn the_boot_drain_verdict_names_the_seal() -> Result<(), Box<dyn Error>> {
let minted = last_identity_store()?;
let log = minted.log();
let mut replayed = minted
.minter
.replay_aggregate_reference(minted.conversation_id, &log)?;
let verdict =
minted
.minter
.drain_restored_candidate_lane(minted.conversation_id, &mut replayed, &log);
if verdict
!= (BootDrainVerdict::Drained {
drains: 1,
sealed: true,
})
{
return Err(format!(
"the last-identity drain's verdict did not name the seal: {verdict:?}"
)
.into());
}
if !replayed.is_closed() {
return Err("the drained authority carries no closed marker".into());
}
Ok(())
}
#[test]
fn enrollment_into_a_sealed_conversation_is_refused_by_type() -> Result<(), Box<dyn Error>> {
let minted = last_identity_store()?;
let booted = ProductionParticipantHandler::new(Arc::clone(&minted.store), seal_config())?;
let refused = booted.handle(
ParticipantConnectionContext::new(ConnectionIncarnation::new(0xF5, 1), MountKind::Tcp),
&mut ParticipantConnectionConversations::default(),
ClientRequest::Enrollment(EnrollmentRequest {
conversation_id: minted.conversation_id,
enrollment_token: EnrollmentToken::new([0xF6; 16]),
}),
);
let Err(ParticipantSemanticError::ConversationSealed { conversation_id }) = refused else {
return Err(format!(
"enrollment into the sealed conversation did not answer with the typed \
ConversationSealed refusal: {refused:?}"
)
.into());
};
if conversation_id != minted.conversation_id {
return Err(format!(
"the sealed refusal named conversation {conversation_id}, not {}",
minted.conversation_id
)
.into());
}
Ok(())
}
#[test]
fn a_sealed_conversation_does_not_refuse_its_neighbours() -> Result<(), Box<dyn Error>> {
let minted = last_identity_store()?;
let booted = ProductionParticipantHandler::new(Arc::clone(&minted.store), seal_config())?;
let neighbour = minted
.conversation_id
.checked_add(1)
.ok_or("neighbour conversation id overflowed")?;
let bound = booted.handle(
ParticipantConnectionContext::new(ConnectionIncarnation::new(0xF7, 1), MountKind::Tcp),
&mut ParticipantConnectionConversations::default(),
ClientRequest::Enrollment(EnrollmentRequest {
conversation_id: neighbour,
enrollment_token: EnrollmentToken::new([0xF8; 16]),
}),
)?;
if !matches!(bound, ServerValue::EnrollBound(_)) {
return Err(
format!("a sealed neighbour refused an unrelated enrollment: {bound:?}").into(),
);
}
Ok(())
}
#[test]
fn a_genesis_only_conversation_still_enrolls_as_ordinary_flow() -> Result<(), Box<dyn Error>> {
const GENESIS_ONLY: u64 = 811;
let store: Arc<dyn DurableStore> = Arc::new(open_ephemeral(1)?);
{
let writer = ProductionParticipantHandler::new(Arc::clone(&store), seal_config())?;
let log = OperationLog::new(Arc::clone(&store), GENESIS_ONLY);
let appender = LogAppender {
log: &log,
registry: &writer.registry,
conversation_id: GENESIS_ONLY,
};
let mut authority = ConversationAuthority::empty(GENESIS_ONLY);
authority.ensure_genesis(&appender)?;
if authority.next_log_sequence != 1 {
return Err("the genesis-only cut appended more than the genesis row".into());
}
drop(writer);
}
let booted = ProductionParticipantHandler::new(Arc::clone(&store), seal_config())?;
let cell = booted.cell(GENESIS_ONLY)?;
let owner = cell
.lock()
.map_err(|_| "genesis-only owner lock was poisoned")?;
let authority = owner.as_ref().ok_or("genesis-only owner was absent")?;
if !authority.tokens.is_empty() || authority.frontier().is_some() {
return Err("the genesis-only prestate is not the tokens-empty frontier-None shape".into());
}
if authority.is_closed() {
return Err("a genesis-only conversation was marked Closed".into());
}
drop(owner);
let bound = booted.handle(
ParticipantConnectionContext::new(ConnectionIncarnation::new(0xF9, 1), MountKind::Tcp),
&mut ParticipantConnectionConversations::default(),
ClientRequest::Enrollment(EnrollmentRequest {
conversation_id: GENESIS_ONLY,
enrollment_token: EnrollmentToken::new([0xFA; 16]),
}),
)?;
if !matches!(bound, ServerValue::EnrollBound(_)) {
return Err(format!(
"a Genesis-only conversation did not enroll as ordinary flow: {bound:?}"
)
.into());
}
Ok(())
}