use liminal_protocol::lifecycle::{
BindingState, MarkerAckDecision, MarkerProofState, PresentedIdentity, RecipientAckObligations,
apply_marker_ack, apply_marker_ack_frontier,
};
use liminal_protocol::wire::ServerValue;
use super::super::facts::Digest;
use super::super::log::{StoredAck, StoredBindingEpoch};
use super::super::marker_progress::marker_replay_progress;
use super::super::observer_progress::ObserverProgressSourceMetadata;
use super::super::outbox_log::StoredMarkerAckCommitted;
use super::super::state::{ConversationAuthority, StateError};
use super::binding_fate::progress_pending_marker_binding_fate;
impl ConversationAuthority {
pub(in crate::server::participant::production) fn replay_zero_debt_ack_row(
&mut self,
request: StoredAck,
receiving_epoch: StoredBindingEpoch,
contiguously_available_through: u64,
ack_obligations: Option<(RecipientAckObligations, u64)>,
) -> Result<(), StateError> {
let (obligations, reconciled_available_through) = ack_obligations.ok_or_else(|| {
StateError::invariant("zero-debt ack replay is missing recipient obligations")
})?;
self.replay_zero_debt_ack(
request,
receiving_epoch,
contiguously_available_through,
reconciled_available_through,
&obligations,
)
}
fn replay_zero_debt_ack(
&mut self,
request: StoredAck,
receiving_epoch: StoredBindingEpoch,
contiguously_available_through: u64,
reconciled_available_through: u64,
obligations: &RecipientAckObligations,
) -> Result<(), StateError> {
if contiguously_available_through != reconciled_available_through {
return Err(StateError::invariant(format!(
"durable zero-debt ack availability {contiguously_available_through} differs from reconciled recipient availability {reconciled_available_through}"
)));
}
let request = request.to_request()?;
let outcome = self.ack_commit(
&request,
receiving_epoch,
obligations,
contiguously_available_through,
None,
)?;
if !matches!(outcome.value, ServerValue::AckCommitted(_)) {
return Err(StateError::invariant(
"durable zero-debt ack entry replayed to a non-committed decision",
));
}
self.advance_log_head()?;
Ok(())
}
pub(in crate::server::participant::production) fn replay_marker_ack_extension(
&mut self,
row: &StoredMarkerAckCommitted,
) -> Result<(), StateError> {
if row.request.conversation_id != self.conversation_id
|| row.offered_marker_delivery_seq != row.request.marker_delivery_seq
|| row.receiving_binding_epoch != row.delivered_binding_epoch
{
return Err(StateError::invariant(
"stored MarkerAck request and delivery witness disagree",
));
}
let progress = marker_replay_progress(self, row)?;
let identity = self
.slots
.get(&row.request.participant_id)
.map_or(PresentedIdentity::Absent, |slot| {
PresentedIdentity::<Digest, Digest, Digest>::Live(&slot.member)
});
let detached = BindingState::Detached;
let binding = self
.slots
.get(&row.request.participant_id)
.map_or(&detached, |slot| &slot.binding);
let cursor = self
.slots
.get(&row.request.participant_id)
.map_or(0, |slot| slot.member.cursor());
if cursor >= row.offered_marker_delivery_seq {
return Ok(());
}
let marker_state = MarkerProofState::new(
cursor,
false,
Some(row.offered_marker_delivery_seq),
row.delivered_binding_epoch,
Some(progress),
);
let MarkerAckDecision::Commit(commit) = apply_marker_ack(
identity,
binding,
row.receiving_binding_epoch,
&row.request,
&marker_state,
) else {
return Err(StateError::invariant(
"stored MarkerAck replayed to a non-commit decision",
));
};
if commit.canonical_request() != row.request
|| commit.receiving_binding_epoch() != row.receiving_binding_epoch
|| commit.offered_marker_delivery_seq() != row.offered_marker_delivery_seq
|| commit.delivered_binding_epoch() != row.delivered_binding_epoch
|| commit.from_cursor() != row.from_cursor
|| commit.resulting_cursor() != row.resulting_cursor
{
return Err(StateError::invariant(
"stored MarkerAck post-transition audit drifted",
));
}
let observer_projection = commit.observer_progress_projection();
let metadata = ObserverProgressSourceMetadata::marker_ack(
row.base_log_head,
row.extension_sequence,
row.request.conversation_id,
row.request.participant_id,
row.request.marker_delivery_seq,
row.resulting_cursor,
);
let transitioned =
apply_marker_ack_frontier(self.take_frontier()?, commit).map_err(|failure| {
StateError::invariant(format!(
"stored MarkerAck frontier transition failed: {:?}",
failure.error()
))
})?;
let (commit, frontier) = transitioned.into_parts();
let slot = self
.slots
.get_mut(&row.request.participant_id)
.ok_or_else(|| StateError::invariant("stored MarkerAck participant is absent"))?;
progress_pending_marker_binding_fate(slot, &commit)?;
let outcome = commit.apply_to(&mut slot.member).map_err(|error| {
StateError::invariant(format!("stored MarkerAck cursor commit failed: {error:?}"))
})?;
let request = outcome.request();
if request.conversation_id != row.request.conversation_id
|| request.participant_id != row.request.participant_id
|| request.capability_generation != row.request.capability_generation
|| request.marker_delivery_seq != row.request.marker_delivery_seq
{
return Err(StateError::invariant(
"stored MarkerAck outcome request drifted",
));
}
self.install_frontier(frontier)?;
self.record_observer_progress_projection(observer_projection, metadata)?;
Ok(())
}
}