liminal_protocol/lifecycle/operations/
marker_drain.rs1use alloc::vec::Vec;
10
11use crate::wire::{ParticipantDelivery, ParticipantRecord};
12
13use super::super::{
14 ClaimFrontiers, ClosureAccounting, ClosureState, Event, MarkerDelivery, ObserverProjection,
15 StoredEdge,
16 claim_frontier::{MarkerDrainCoreError, ValidatedMarkerRecord},
17};
18use super::RetainedRecordCharge;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum MarkerDrainError {
23 NoCandidate,
25 BindingTerminalFirst,
27 SequenceNotNext,
29 CurrentEdgeMismatch,
31 CausalMajorNotAllocated,
33 MissingOrderCandidate,
35 ResultingLedger,
37 AuthorityMismatch,
39 MarkerChargeKey,
41 MarkerEntryCharge,
43 ResultingAccounting,
45}
46
47#[derive(Debug, PartialEq, Eq)]
72pub struct MarkerDeliveryProjection {
73 delivery: ParticipantDelivery,
74}
75
76impl MarkerDeliveryProjection {
77 #[must_use]
79 pub const fn delivery(&self) -> &ParticipantDelivery {
80 &self.delivery
81 }
82
83 #[must_use]
85 pub fn into_delivery(self) -> ParticipantDelivery {
86 self.delivery
87 }
88}
89
90#[derive(Debug, PartialEq, Eq)]
106pub struct MarkerDrainCommit {
107 frontiers: ClaimFrontiers,
108 closure_accounting: ClosureAccounting,
109 retained_charges: Vec<RetainedRecordCharge>,
110 marker_successor: StoredEdge,
111 delivery_projection: MarkerDeliveryProjection,
112 record: ValidatedMarkerRecord,
113}
114
115impl MarkerDrainCommit {
116 #[must_use]
118 pub const fn frontiers(&self) -> &ClaimFrontiers {
119 &self.frontiers
120 }
121
122 #[must_use]
124 pub const fn closure(&self) -> ClosureState {
125 self.closure_accounting.state()
126 }
127
128 #[must_use]
130 pub const fn closure_accounting(&self) -> ClosureAccounting {
131 self.closure_accounting
132 }
133
134 #[must_use]
136 pub fn retained_charges(&self) -> &[RetainedRecordCharge] {
137 &self.retained_charges
138 }
139
140 #[must_use]
145 pub const fn marker_successor(&self) -> StoredEdge {
146 self.marker_successor
147 }
148
149 #[must_use]
151 pub const fn delivery_projection(&self) -> &MarkerDeliveryProjection {
152 &self.delivery_projection
153 }
154
155 #[must_use]
161 pub fn into_parts(
162 self,
163 ) -> (
164 ClaimFrontiers,
165 ClosureAccounting,
166 Vec<RetainedRecordCharge>,
167 StoredEdge,
168 MarkerDeliveryProjection,
169 ) {
170 self.record.consume();
171 (
172 self.frontiers,
173 self.closure_accounting,
174 self.retained_charges,
175 self.marker_successor,
176 self.delivery_projection,
177 )
178 }
179
180 #[cfg(test)]
182 pub(super) fn into_record_for_test(self) -> ValidatedMarkerRecord {
183 self.record
184 }
185}
186
187pub fn drain_next_marker(
203 frontiers: ClaimFrontiers,
204 current_accounting: ClosureAccounting,
205 mut retained_charges: Vec<RetainedRecordCharge>,
206 marker_charge: RetainedRecordCharge,
207) -> Result<MarkerDrainCommit, MarkerDrainError> {
208 let core = frontiers.drain_next_marker_core().map_err(map_core_error)?;
209 let (frontiers, candidate, record) = core.into_parts();
210 let candidate_conversation_id = candidate.conversation_id();
211 let candidate_participant = candidate.participant_id();
212 let candidate_sequence = candidate.delivery_seq();
213 let candidate_target = candidate.target_binding();
214 let candidate_provenance = candidate.provenance();
215 let delivery_projection = MarkerDeliveryProjection {
216 delivery: ParticipantDelivery {
217 conversation_id: candidate_conversation_id,
218 delivery_seq: candidate_sequence,
219 record: ParticipantRecord::HistoryCompacted {
220 affected_participant_id: candidate_participant,
221 abandoned_after: candidate.abandoned_after(),
222 abandoned_through: candidate.abandoned_through(),
223 physical_floor_at_decision: candidate.physical_floor_at_decision(),
224 },
225 },
226 };
227 let marker_successor = MarkerDelivery::successor_from_validated_candidate(candidate);
228 if record.conversation_id() != candidate_conversation_id
229 || record.participant_id() != candidate_participant
230 || record.delivery_seq() != candidate_sequence
231 || record.target_binding() != candidate_target
232 || record.provenance() != candidate_provenance
233 {
234 return Err(MarkerDrainError::AuthorityMismatch);
235 }
236 if marker_charge.delivery_seq() != record.delivery_seq()
237 || marker_charge.admission_order() != record.admission_order()
238 {
239 return Err(MarkerDrainError::MarkerChargeKey);
240 }
241 if marker_charge.encoded_charge().entries != 1 {
242 return Err(MarkerDrainError::MarkerEntryCharge);
243 }
244 let closure = apply_marker_append(current_accounting.state(), candidate_sequence)?;
245 let closure_accounting = ClosureAccounting::try_new(
246 closure,
247 current_accounting.marker_capacity_credits(),
248 current_accounting.marker_anchors(),
249 current_accounting.edge_sequence_claims(),
250 current_accounting.edge_order_position_claims(),
251 current_accounting.edge_k_remaining(),
252 current_accounting.baseline(),
253 current_accounting.configured_cap(),
254 current_accounting.episode_churn_used(),
255 current_accounting.episode_churn_limit(),
256 )
257 .map_err(|_| MarkerDrainError::ResultingAccounting)?;
258 retained_charges.push(marker_charge);
259 if retained_charges.len() != frontiers.retained_records().len()
260 || !retained_charges
261 .iter()
262 .zip(frontiers.retained_records())
263 .all(|(charge, retained)| {
264 charge.delivery_seq() == retained.delivery_seq
265 && charge.admission_order() == retained.admission_order
266 })
267 {
268 return Err(MarkerDrainError::MarkerChargeKey);
269 }
270 Ok(MarkerDrainCommit {
271 frontiers,
272 closure_accounting,
273 retained_charges,
274 marker_successor,
275 delivery_projection,
276 record,
277 })
278}
279
280fn apply_marker_append(
281 current: ClosureState,
282 marker_delivery_seq: u64,
283) -> Result<ClosureState, MarkerDrainError> {
284 let event = Event::marker_appended(marker_delivery_seq, marker_delivery_seq);
285 match current {
286 ClosureState::Clear => Ok(ClosureState::Clear),
287 ClosureState::Owed {
288 debt,
289 edge: StoredEdge::ObserverProjection(projection),
290 } => {
291 let successor = projection
292 .later_projection_after_marker(
293 &event,
294 debt,
295 ObserverProjection::new(marker_delivery_seq),
296 )
297 .ok_or(MarkerDrainError::CurrentEdgeMismatch)?;
298 projection
299 .marker_appended(debt, event, successor)
300 .map_err(|_| MarkerDrainError::CurrentEdgeMismatch)
301 }
302 ClosureState::Owed {
303 debt,
304 edge: StoredEdge::PhysicalCompaction(compaction),
305 } => compaction
306 .marker_appended(debt, event)
307 .map_err(|_| MarkerDrainError::CurrentEdgeMismatch),
308 ClosureState::Owed { .. } => Err(MarkerDrainError::CurrentEdgeMismatch),
309 }
310}
311
312const fn map_core_error(error: MarkerDrainCoreError) -> MarkerDrainError {
313 match error {
314 MarkerDrainCoreError::NoCandidate => MarkerDrainError::NoCandidate,
315 MarkerDrainCoreError::BindingTerminalFirst => MarkerDrainError::BindingTerminalFirst,
316 MarkerDrainCoreError::SequenceNotNext => MarkerDrainError::SequenceNotNext,
317 MarkerDrainCoreError::CausalMajorNotAllocated => MarkerDrainError::CausalMajorNotAllocated,
318 MarkerDrainCoreError::MissingOrderCandidate => MarkerDrainError::MissingOrderCandidate,
319 MarkerDrainCoreError::ResultingLedger => MarkerDrainError::ResultingLedger,
320 }
321}