krabka_verified/compaction/
retain_decision.rs1use {
2 super::{BatchMeta, CompactionDecodeStep, RecordMeta, RetainDecision, TxnDataState},
3 creusot_std::prelude::*,
4};
5
6#[ensures((result == CompactionDecodeStep::Done) == (remaining_before@ == 0))]
12#[ensures((result == CompactionDecodeStep::Continue) == (remaining_before@ > 0
13 && decode_succeeded
14 && remaining_after@ < remaining_before@))]
15#[ensures((result == CompactionDecodeStep::Corrupt) == (remaining_before@ > 0
16 && (!decode_succeeded || remaining_after@ >= remaining_before@)))]
17#[must_use]
18pub const fn compaction_decode_step(
19 remaining_before: usize,
20 decode_succeeded: bool,
21 remaining_after: usize,
22) -> CompactionDecodeStep {
23 if remaining_before == 0 {
24 CompactionDecodeStep::Done
25 } else if decode_succeeded && remaining_after < remaining_before {
26 CompactionDecodeStep::Continue
27 } else {
28 CompactionDecodeStep::Corrupt
29 }
30}
31
32#[cfg(creusot)]
36#[cfg_attr(test, mutants::skip)]
37#[logic]
38pub fn compute_horizon_model(now_ms: i64, delete_retention_ms: i64) -> Int {
39 pearlite! {
40 if now_ms@ + delete_retention_ms@ > 9223372036854775807 {
41 9223372036854775807
42 } else if now_ms@ + delete_retention_ms@ < -9223372036854775807 - 1 {
43 -9223372036854775807 - 1
44 } else {
45 now_ms@ + delete_retention_ms@
46 }
47 }
48}
49
50#[ensures(result@ == compute_horizon_model(now_ms, delete_retention_ms))]
56#[must_use]
57pub const fn compute_horizon(now_ms: i64, delete_retention_ms: i64) -> i64 {
58 now_ms.saturating_add(delete_retention_ms)
59}
60
61#[cfg(creusot)]
63#[cfg_attr(test, mutants::skip)]
64#[logic(open)]
65fn horizon_decision_matches(
66 existing: Option<i64>,
67 now: i64,
68 retention: i64,
69 decision: RetainDecision,
70) -> bool {
71 pearlite! { match existing {
72 None => exists<h: i64> decision == RetainDecision::SetHorizon(h)
73 && h@ == compute_horizon_model(now, retention),
74 Some(h) => decision == if now@ >= h@ { RetainDecision::Delete } else { RetainDecision::Keep },
75 } }
76}
77
78#[ensures(horizon_decision_matches(existing, now_ms, delete_retention_ms, result))]
79const fn horizon_retention(
80 existing: Option<i64>,
81 now_ms: i64,
82 delete_retention_ms: i64,
83) -> RetainDecision {
84 match existing {
85 Some(h) if now_ms >= h => RetainDecision::Delete,
86 Some(_) => RetainDecision::Keep,
87 None => RetainDecision::SetHorizon(compute_horizon(now_ms, delete_retention_ms)),
88 }
89}
90
91#[ensures(batch.is_control && (txn == TxnDataState::DataSurvives || txn == TxnDataState::NotTransactional)
99 ==> result == RetainDecision::Keep)]
100#[ensures(batch.is_control && txn == TxnDataState::DataFullyGone
101 ==> horizon_decision_matches(batch.existing_horizon, now_ms, delete_retention_ms, result))]
102#[ensures(!batch.is_control && !rec.has_key ==> result == RetainDecision::Delete)]
103#[ensures(!batch.is_control && rec.has_key && !is_newest_for_key ==> result == RetainDecision::Delete)]
104#[ensures(!batch.is_control && rec.has_key && is_newest_for_key && rec.has_value
105 ==> result == RetainDecision::Keep)]
106#[ensures(!batch.is_control && rec.has_key && is_newest_for_key && !rec.has_value
107 ==> horizon_decision_matches(batch.existing_horizon, now_ms, delete_retention_ms, result))]
108#[must_use]
109pub const fn retain_decision(
110 rec: RecordMeta,
111 batch: BatchMeta,
112 is_newest_for_key: bool,
113 txn: TxnDataState,
114 now_ms: i64,
115 delete_retention_ms: i64,
116) -> RetainDecision {
117 if batch.is_control {
118 return match txn {
119 TxnDataState::DataSurvives | TxnDataState::NotTransactional => RetainDecision::Keep,
120 TxnDataState::DataFullyGone => {
121 horizon_retention(batch.existing_horizon, now_ms, delete_retention_ms)
122 }
123 };
124 }
125 if !rec.has_key {
126 return RetainDecision::Delete;
127 }
128 if !is_newest_for_key {
129 return RetainDecision::Delete;
130 }
131 if rec.has_value {
132 return RetainDecision::Keep;
133 }
134 horizon_retention(batch.existing_horizon, now_ms, delete_retention_ms)
136}