use liminal_protocol::lifecycle::{
CredentialAttachCapacityCounters, CredentialAttachCapacityDecision, ReceiptDeadlines,
select_credential_attach_capacity,
};
use liminal_protocol::wire::{
CredentialAttachRequest, CredentialAttachResponse, ReceiptCapacityScope,
};
use super::barrier::OperationFacts;
use super::capacity::{
CapacityReservation, OccupancyEntry, ReservationEffects, ResourceKind, ScopeCounter,
ServerCapacity, Stage8Choice, Stage8Outcome, scope_counter,
};
use super::ops_attach::attach_envelope;
use super::state::{ConversationAuthority, Slot, StateError};
impl ConversationAuthority {
pub(super) fn attach_stage8<'cap>(
&self,
request: &CredentialAttachRequest,
slot: &Slot,
operation_facts: &OperationFacts,
server_capacity: &'cap ServerCapacity,
deadlines: &ReceiptDeadlines,
) -> Result<AttachStage8<'cap>, StateError> {
let now = u128::from(operation_facts.now_ms);
let limits = operation_facts.receipt_limits;
let live_receipt_participant_occupied = slot.live_receipt_occupancy(now);
let provenance_participant_occupied = slot.provenance_occupancy(now)?;
let provenance_conversation_occupied = self.provenance_occupancy(now)?;
let token = request.attach_attempt_token.into_bytes();
let effects = ReservationEffects {
conversation_id: self.conversation_id,
identity_reserved: false,
inserts: vec![
OccupancyEntry {
expires_at: deadlines.receipt_expires_at(),
conversation_id: self.conversation_id,
participant_id: request.participant_id,
kind: ResourceKind::AttachReceipt,
token,
},
OccupancyEntry {
expires_at: deadlines.provenance_expires_at(),
conversation_id: self.conversation_id,
participant_id: request.participant_id,
kind: ResourceKind::AttachProvenance,
token,
},
],
};
let mut retire = Vec::new();
if let Some(previous) = slot.attach.as_ref() {
retire.push(OccupancyEntry {
expires_at: previous.receipt_expires_at,
conversation_id: self.conversation_id,
participant_id: request.participant_id,
kind: ResourceKind::AttachReceipt,
token: previous.token.into_bytes(),
});
}
if slot.enrollment_receipt_ended.is_none() {
retire.push(OccupancyEntry {
expires_at: slot.enrollment_receipt_expires_at,
conversation_id: self.conversation_id,
participant_id: request.participant_id,
kind: ResourceKind::EnrollmentReceipt,
token: self.enrollment_token_bytes(request.participant_id)?,
});
}
let outcome = server_capacity.admit(now, effects, |server| {
let counters = match attach_scope_counters(
request,
limits,
server,
live_receipt_participant_occupied,
provenance_conversation_occupied,
provenance_participant_occupied,
)? {
Ok(counters) => counters,
Err(response) => return Ok(Stage8Choice::Refuse(response)),
};
match select_credential_attach_capacity(request, counters) {
CredentialAttachCapacityDecision::Commit(_) => Ok(Stage8Choice::Admit(())),
CredentialAttachCapacityDecision::Respond(response) => {
Ok(Stage8Choice::Refuse(response))
}
}
})?;
Ok(match outcome {
Stage8Outcome::Refused(response) => AttachStage8::Refused(response),
Stage8Outcome::Reserved(reservation, ()) => AttachStage8::Reserved {
reservation,
retire,
},
})
}
}
fn attach_scope_counters(
request: &CredentialAttachRequest,
limits: super::barrier::ReceiptCapacityLimits,
server: super::capacity::ServerOccupancy,
live_receipt_participant_occupied: u64,
provenance_conversation_occupied: u64,
provenance_participant_occupied: u64,
) -> Result<Result<CredentialAttachCapacityCounters, CredentialAttachResponse>, StateError> {
let ordered = [
(
limits.live_receipts_server,
server.live_receipts,
ReceiptCapacityScope::LiveReceiptServer,
),
(
limits.live_receipts_per_participant,
live_receipt_participant_occupied,
ReceiptCapacityScope::LiveReceiptParticipant,
),
(
limits.provenance_server,
server.provenance,
ReceiptCapacityScope::ProvenanceServer,
),
(
limits.provenance_per_conversation,
provenance_conversation_occupied,
ReceiptCapacityScope::ProvenanceConversation,
),
(
limits.provenance_per_participant,
provenance_participant_occupied,
ReceiptCapacityScope::ProvenanceParticipant,
),
];
let mut counters = Vec::with_capacity(ordered.len());
let mut first_full: Option<(ReceiptCapacityScope, u64, u64)> = None;
for (limit, occupied, scope) in ordered {
match scope_counter(limit, occupied)? {
ScopeCounter::Valid(counter) => {
if first_full.is_none() && counter.is_full() {
first_full = Some((scope, counter.limit(), counter.occupied()));
}
counters.push(counter);
}
ScopeCounter::OverLimit { limit, occupied } => {
let (scope, limit, occupied) = first_full.unwrap_or((scope, limit, occupied));
return Ok(Err(CredentialAttachResponse::receipt_capacity_exceeded(
attach_envelope(request),
scope,
limit,
occupied,
)));
}
}
}
let [lrs, lrp, ps, pc, pp]: [liminal_protocol::lifecycle::CapacityCounter; 5] = counters
.try_into()
.map_err(|_| StateError::invariant("attach stage-8 scope construction lost a counter"))?;
Ok(Ok(CredentialAttachCapacityCounters::new(
lrs, lrp, ps, pc, pp,
)))
}
pub(super) enum AttachStage8<'cap> {
Refused(CredentialAttachResponse),
Reserved {
reservation: CapacityReservation<'cap>,
retire: Vec<OccupancyEntry>,
},
}