use std::error::Error;
use std::sync::Arc;
use liminal::durability::{DurableStore, open_ephemeral};
use liminal_protocol::lifecycle::{ImmutableSequenceCandidate, PrecedenceCondition};
use liminal_protocol::wire::{
AttachAttemptToken, ClientRequest, ConnectionIncarnation, CredentialAttachRequest,
DetachAttemptToken, DetachRequest, EnrollBound, EnrollmentRequest,
EnrollmentSettlementBackpressure, EnrollmentToken, Generation, MarkerSettlementBackpressure,
RecordAdmission, RecordAdmissionAttemptToken, ServerValue,
};
use super::ProductionParticipantHandler;
use super::tests::dispatch;
use super::tests_marker_ack_fixture::marker_fixture_config;
const CONVERSATION: u64 = 1;
struct SettlementFixture {
handler: ProductionParticipantHandler,
connection: ConnectionIncarnation,
participant: u64,
generation: Generation,
secret: liminal_protocol::wire::AttachSecret,
settlement_epoch: u64,
records_committed: u64,
}
fn store() -> Result<Arc<dyn DurableStore>, Box<dyn Error>> {
Ok(Arc::new(open_ephemeral(1)?))
}
fn precedence_condition(
handler: &ProductionParticipantHandler,
) -> Result<Option<(PrecedenceCondition, bool)>, Box<dyn Error>> {
let cell = handler.cell(CONVERSATION)?;
let owner = cell
.lock()
.map_err(|_| "conversation authority lock was poisoned")?;
let authority = owner
.as_ref()
.ok_or("conversation authority must be restored")?;
let Some(frontier) = authority.frontier() else {
return Ok(None);
};
let frontiers = frontier.frontiers();
let condition = (
frontiers.precedence_condition(),
frontiers.live_transition_blocked(),
);
drop(owner);
Ok(Some(condition))
}
fn marker_candidate_seq(
handler: &ProductionParticipantHandler,
) -> Result<Option<u64>, Box<dyn Error>> {
let cell = handler.cell(CONVERSATION)?;
let owner = cell
.lock()
.map_err(|_| "conversation authority lock was poisoned")?;
let authority = owner
.as_ref()
.ok_or("conversation authority must be restored")?;
let seq = authority.frontier().and_then(|frontier| {
frontier
.frontiers()
.sequence()
.immutable_candidates()
.first()
.and_then(|candidate| match candidate {
ImmutableSequenceCandidate::Marker(marker) => Some(marker.delivery_seq),
ImmutableSequenceCandidate::BindingTerminal { .. } => None,
})
});
drop(owner);
Ok(seq)
}
fn enroll(
handler: &ProductionParticipantHandler,
connection: ConnectionIncarnation,
token: [u8; 16],
) -> Result<EnrollBound, Box<dyn Error>> {
match dispatch(
handler,
connection,
ClientRequest::Enrollment(EnrollmentRequest {
conversation_id: CONVERSATION,
enrollment_token: EnrollmentToken::new(token),
}),
)? {
ServerValue::EnrollBound(bound) => Ok(bound),
other => Err(format!("enrollment must bind, got {other:?}").into()),
}
}
fn attach(fixture: &SettlementFixture, token: [u8; 16]) -> Result<ServerValue, Box<dyn Error>> {
dispatch(
&fixture.handler,
fixture.connection,
ClientRequest::CredentialAttach(CredentialAttachRequest {
conversation_id: CONVERSATION,
participant_id: fixture.participant,
capability_generation: fixture.generation,
attach_secret: fixture.secret,
attach_attempt_token: AttachAttemptToken::new(token),
accept_marker_delivery_seq: None,
}),
)
}
fn commit_record(
handler: &ProductionParticipantHandler,
connection: ConnectionIncarnation,
participant: u64,
generation: Generation,
nonce: u8,
) -> Result<ServerValue, Box<dyn Error>> {
dispatch(
handler,
connection,
ClientRequest::RecordAdmission(RecordAdmission {
conversation_id: CONVERSATION,
participant_id: participant,
capability_generation: generation,
record_admission_attempt_token: RecordAdmissionAttemptToken::new([nonce; 16]),
payload: vec![nonce],
}),
)
}
fn settlement_window() -> Result<SettlementFixture, Box<dyn Error>> {
let handler = ProductionParticipantHandler::new(store()?, marker_fixture_config())?;
let connection = ConnectionIncarnation::new(1, 1);
let lagging = ConnectionIncarnation::new(2, 1);
let bound = enroll(&handler, connection, [1; 16])?;
let lagging_bound = enroll(&handler, lagging, [2; 16])?;
let lagging_participant = lagging_bound.participant_id();
let lagging_attached = dispatch(
&handler,
lagging,
ClientRequest::CredentialAttach(CredentialAttachRequest {
conversation_id: CONVERSATION,
participant_id: lagging_participant,
capability_generation: lagging_bound.capability_generation(),
attach_secret: lagging_bound.attach_secret(),
attach_attempt_token: AttachAttemptToken::new([0x11; 16]),
accept_marker_delivery_seq: None,
}),
)?;
if !matches!(lagging_attached, ServerValue::AttachBound(_)) {
return Err(
format!("the lagging member's attach must bind, got {lagging_attached:?}").into(),
);
}
let participant = bound.participant_id();
let attached = dispatch(
&handler,
connection,
ClientRequest::CredentialAttach(CredentialAttachRequest {
conversation_id: CONVERSATION,
participant_id: participant,
capability_generation: bound.capability_generation(),
attach_secret: bound.attach_secret(),
attach_attempt_token: AttachAttemptToken::new([0x10; 16]),
accept_marker_delivery_seq: None,
}),
)?;
let ServerValue::AttachBound(attached) = attached else {
return Err(format!("the fixture's attach must bind, got {attached:?}").into());
};
let generation = attached.capability_generation();
let secret = attached.attach_secret();
for nonce in 1..=12_u8 {
let committed = commit_record(&handler, connection, participant, generation, nonce)?;
if !matches!(committed, ServerValue::RecordCommitted(_)) {
return Err(format!(
"the fixture's record {nonce} must commit to generate marker debt, got {committed:?}"
)
.into());
}
if let Some(settlement_epoch) = marker_candidate_seq(&handler)? {
return Ok(SettlementFixture {
handler,
connection,
participant,
generation,
secret,
settlement_epoch,
records_committed: u64::from(nonce),
});
}
}
Err("the fixture never opened a marker-settlement window".into())
}
#[test]
fn the_fixture_opens_a_real_condition_two_settlement_window() -> Result<(), Box<dyn Error>> {
let fixture = settlement_window()?;
assert!(fixture.records_committed >= 1);
let (condition, blocked) = precedence_condition(&fixture.handler)?
.ok_or("the conversation must hold a live frontier")?;
assert!(
blocked,
"the seam's own guard must agree a live transition is blocked"
);
assert_eq!(
condition,
PrecedenceCondition::MarkerDrain {
settlement_epoch: fixture.settlement_epoch
},
"the seam must classify this as the marker-drain condition, not a binding terminal, \
an armed recovery, or an unclassified state"
);
Ok(())
}
#[test]
fn an_attach_inside_the_settlement_window_hears_the_labelled_row() -> Result<(), Box<dyn Error>> {
let fixture = settlement_window()?;
let value = attach(&fixture, [0x51; 16])?;
match value {
ServerValue::MarkerSettlementBackpressure(
MarkerSettlementBackpressure::CredentialAttach {
conversation_id,
refused_epoch,
},
) => {
assert_eq!(conversation_id, CONVERSATION);
assert_eq!(
refused_epoch, fixture.settlement_epoch,
"refused_epoch is load-bearing: the stage-11 retry matches it against \
MarkerSettled's own epoch, so it must be the head candidate's delivery sequence"
);
}
other => {
return Err(format!(
"a settlement-window attach must hear MarkerSettlementBackpressure, got {other:?}"
)
.into());
}
}
Ok(())
}
#[test]
fn a_detach_inside_the_settlement_window_hears_the_labelled_row() -> Result<(), Box<dyn Error>> {
let fixture = settlement_window()?;
let value = dispatch(
&fixture.handler,
fixture.connection,
ClientRequest::Detach(DetachRequest {
conversation_id: CONVERSATION,
participant_id: fixture.participant,
capability_generation: fixture.generation,
detach_attempt_token: DetachAttemptToken::new([0x52; 16]),
}),
)?;
match value {
ServerValue::MarkerSettlementBackpressure(MarkerSettlementBackpressure::Detach {
conversation_id,
refused_epoch,
}) => {
assert_eq!(conversation_id, CONVERSATION);
assert_eq!(refused_epoch, fixture.settlement_epoch);
}
other => {
return Err(format!(
"a settlement-window detach must hear MarkerSettlementBackpressure, got {other:?}"
)
.into());
}
}
Ok(())
}
#[test]
fn a_subsequent_enrollment_inside_the_window_hears_the_unlabelled_row() -> Result<(), Box<dyn Error>>
{
let fixture = settlement_window()?;
let stranger = ConnectionIncarnation::new(2, 2);
let value = dispatch(
&fixture.handler,
stranger,
ClientRequest::Enrollment(EnrollmentRequest {
conversation_id: CONVERSATION,
enrollment_token: EnrollmentToken::new([0x53; 16]),
}),
)?;
match value {
ServerValue::EnrollmentSettlementBackpressure(EnrollmentSettlementBackpressure {
conversation_id,
}) => assert_eq!(conversation_id, CONVERSATION),
other => {
return Err(format!(
"a settlement-window subsequent enrollment must hear \
EnrollmentSettlementBackpressure, got {other:?}"
)
.into());
}
}
assert!(
!matches!(
dispatch(
&fixture.handler,
stranger,
ClientRequest::Enrollment(EnrollmentRequest {
conversation_id: CONVERSATION,
enrollment_token: EnrollmentToken::new([0x54; 16]),
}),
)?,
ServerValue::MarkerSettlementBackpressure(_)
),
"the enrollment wrapper must NEVER mint the labelled row"
);
Ok(())
}
#[test]
fn a_presented_settlement_refusal_restores_every_consumed_authority() -> Result<(), Box<dyn Error>>
{
let fixture = settlement_window()?;
let before = super::tests_history::authority_snapshot(&fixture.handler, CONVERSATION)?;
let value = attach(&fixture, [0x55; 16])?;
assert!(
matches!(value, ServerValue::MarkerSettlementBackpressure(_)),
"the refusal under test must be the presented one, got {value:?}"
);
let after = super::tests_history::authority_snapshot(&fixture.handler, CONVERSATION)?;
assert_eq!(
before, after,
"§0.16 presentation law: the slot entry, frontier, and prepared finalizer state \
readable before the request must be readable IDENTICALLY after the refusal"
);
Ok(())
}
#[test]
fn a_settlement_refusal_returns_the_position_allocators_in_step_with_the_frontier()
-> Result<(), Box<dyn Error>> {
let fixture = settlement_window()?;
let before = position_and_watermark(&fixture.handler)?;
assert_eq!(
before.0.1,
before.1 + 1,
"the fixture must start in step, or the pin below measures nothing"
);
let refusal = attach(&fixture, [0x56; 16])?;
assert!(matches!(
refusal,
ServerValue::MarkerSettlementBackpressure(_)
));
let after = position_and_watermark(&fixture.handler)?;
assert_eq!(
before, after,
"the position allocators and the frontier watermark must be identical across a \
refusal that committed nothing"
);
assert_eq!(
after.0.1,
after.1 + 1,
"the next delivery sequence must still be exactly one past the high watermark -- \
this is the predicate the seam's record-position check enforces on the retry"
);
let again = attach(&fixture, [0x57; 16])?;
assert_eq!(refusal, again);
assert_eq!(position_and_watermark(&fixture.handler)?, after);
Ok(())
}
fn position_and_watermark(
handler: &ProductionParticipantHandler,
) -> Result<((u64, u64), u64), Box<dyn Error>> {
let cell = handler.cell(CONVERSATION)?;
let owner = cell
.lock()
.map_err(|_| "conversation authority lock was poisoned")?;
let authority = owner
.as_ref()
.ok_or("conversation authority must be restored")?;
let position = authority.next_position_for_test();
let watermark = authority
.frontier()
.ok_or("the conversation must hold a live frontier")?
.frontiers()
.sequence()
.ledger()
.high_watermark();
drop(owner);
Ok((position, watermark))
}
#[test]
fn earlier_stages_still_win_inside_the_settlement_window() -> Result<(), Box<dyn Error>> {
let fixture = settlement_window()?;
let unknown = dispatch(
&fixture.handler,
fixture.connection,
ClientRequest::CredentialAttach(CredentialAttachRequest {
conversation_id: CONVERSATION,
participant_id: fixture.participant + 9_999,
capability_generation: fixture.generation,
attach_secret: fixture.secret,
attach_attempt_token: AttachAttemptToken::new([0x59; 16]),
accept_marker_delivery_seq: None,
}),
)?;
assert!(
matches!(unknown, ServerValue::ParticipantUnknown(_)),
"an earlier-stage refusal must still be heard inside a settlement window, got {unknown:?}"
);
let marker_bearing = dispatch(
&fixture.handler,
fixture.connection,
ClientRequest::CredentialAttach(CredentialAttachRequest {
conversation_id: CONVERSATION,
participant_id: fixture.participant,
capability_generation: fixture.generation,
attach_secret: fixture.secret,
attach_attempt_token: AttachAttemptToken::new([0x5A; 16]),
accept_marker_delivery_seq: Some(fixture.settlement_epoch),
}),
)?;
assert!(
!matches!(marker_bearing, ServerValue::MarkerSettlementBackpressure(_)),
"the marker-bearing refusal is selected ahead of the settlement row, got \
{marker_bearing:?}"
);
assert_eq!(
marker_candidate_seq(&fixture.handler)?,
Some(fixture.settlement_epoch),
"the settlement window must still be open, or both assertions above are vacuous"
);
Ok(())
}
#[test]
fn the_settlement_registry_wakes_only_the_refused_connection() -> Result<(), Box<dyn Error>> {
use std::sync::atomic::AtomicU64;
use liminal_protocol::wire::MarkerSettlementBackpressure;
use crate::server::connection::ReadyWaker;
use crate::server::mount::MountKind;
use crate::server::participant::{
InstalledParticipantService, ParticipantConnectionContext, ParticipantSemanticHandler,
};
const REFUSED_EPOCH: u64 = 11;
let store = store()?;
let config = marker_fixture_config();
let handler: Arc<dyn ParticipantSemanticHandler> = Arc::new(
ProductionParticipantHandler::new(Arc::clone(&store), config)?,
);
let service = InstalledParticipantService::new(handler, store, config.wire_frame_limit)
.map_err(|error| format!("settlement registry fixture failed: {error:?}"))?;
let refused_incarnation = ConnectionIncarnation::new(0xA5, 1);
let bystander_incarnation = ConnectionIncarnation::new(0xA5, 2);
let refused_wakes = Arc::new(AtomicU64::new(0));
let bystander_wakes = Arc::new(AtomicU64::new(0));
let refused_inbox = service.new_publication_inbox();
let bystander_inbox = service.new_publication_inbox();
service.publication_registry().register(
refused_incarnation,
&refused_inbox,
ReadyWaker::for_test(Arc::clone(&refused_wakes)),
)?;
service.publication_registry().register(
bystander_incarnation,
&bystander_inbox,
ReadyWaker::for_test(Arc::clone(&bystander_wakes)),
)?;
service.register_settlement_waiter_for_test(
ParticipantConnectionContext::new(refused_incarnation, MountKind::Tcp),
&ServerValue::MarkerSettlementBackpressure(MarkerSettlementBackpressure::Detach {
conversation_id: CONVERSATION,
refused_epoch: REFUSED_EPOCH,
}),
)?;
service.register_settlement_waiter_for_test(
ParticipantConnectionContext::new(bystander_incarnation, MountKind::Tcp),
&ServerValue::EnrollmentSettlementBackpressure(
liminal_protocol::wire::EnrollmentSettlementBackpressure {
conversation_id: CONVERSATION,
},
),
)?;
assert_eq!(
service.settlement_waiter_count(CONVERSATION),
1,
"only the marker-settlement refusal installs a waiter"
);
service.fire_settlements_for_test(CONVERSATION, &[REFUSED_EPOCH + 1])?;
assert!(
refused_inbox.take_ready()?.marker_settled.is_empty(),
"a fire at an unnamed epoch must wake no one"
);
assert_eq!(
service.settlement_waiter_count(CONVERSATION),
1,
"an unmatched fire must not consume the waiter"
);
service.fire_settlements_for_test(CONVERSATION, &[REFUSED_EPOCH])?;
let refused_batch = refused_inbox.take_ready()?;
let bystander_batch = bystander_inbox.take_ready()?;
assert_eq!(
refused_batch.marker_settled.len(),
1,
"the refused connection must receive exactly one settlement wake"
);
assert_eq!(
refused_batch.marker_settled[0].refused_epoch, REFUSED_EPOCH,
"the wake must carry the epoch the refusal named"
);
assert_eq!(refused_batch.marker_settled[0].conversation_id, CONVERSATION);
assert!(
bystander_batch.marker_settled.is_empty(),
"an uninvolved connection on the same conversation must receive nothing: \
{:?}",
bystander_batch.marker_settled
);
assert_eq!(
service.settlement_waiter_count(CONVERSATION),
0,
"a fired waiter must not survive its own wake"
);
service.fire_settlements_for_test(CONVERSATION, &[REFUSED_EPOCH])?;
assert!(
refused_inbox.take_ready()?.marker_settled.is_empty(),
"a second fire at the same epoch must deliver nothing"
);
Ok(())
}