@@ -9,7 +9,8 @@ use locality_slack::portable::hosted::{
99 MAX_HOSTED_SLACK_CURSOR_BYTES_V1 , MAX_HOSTED_SLACK_POLL_PAGE_BYTES_V1 ,
1010 MAX_HOSTED_SLACK_POLL_PAGE_MESSAGES_V1 , RawHostedSlackMessage , RawHostedSlackNativeSnapshot ,
1111 decode_hosted_slack_history_page_v1, decode_hosted_slack_poll_checkpoint_v1,
12- decode_hosted_slack_poll_checkpoint_v2, decode_hosted_slack_replies_page_v1,
12+ decode_hosted_slack_poll_checkpoint_v2, decode_hosted_slack_poll_checkpoint_v3,
13+ decode_hosted_slack_replies_page_v1,
1314} ;
1415use serde:: Serialize ;
1516
@@ -1184,9 +1185,10 @@ fn incremental_poll_starts_from_applied_candidate_and_reconciles_late_old_root_r
11841185 "the applied candidate must not be duplicated into replay evidence"
11851186 ) ;
11861187 assert_eq ! (
1187- decode_hosted_slack_poll_checkpoint_v2 ( & encoded) . unwrap( ) ,
1188+ decode_hosted_slack_poll_checkpoint_v3 ( & encoded) . unwrap( ) ,
11881189 incremental
11891190 ) ;
1191+ assert ! ( decode_hosted_slack_poll_checkpoint_v2( & encoded) . is_err( ) ) ;
11901192 assert ! ( decode_hosted_slack_poll_checkpoint_v1( & encoded) . is_err( ) ) ;
11911193 let mut wrong_incremental_version = encoded_value. clone ( ) ;
11921194 wrong_incremental_version[ "checkpoint_format_version" ] = 2 . into ( ) ;
@@ -1247,6 +1249,117 @@ fn incremental_poll_starts_from_applied_candidate_and_reconciles_late_old_root_r
12471249 ) ;
12481250}
12491251
1252+ #[ test]
1253+ fn incremental_old_root_sweep_resumes_beyond_evidence_page_bound ( ) {
1254+ let mut applied = checkpoint ( ) ;
1255+ let mut empty_history = history_page ( ) ;
1256+ empty_history. next_cursor = None ;
1257+ empty_history. messages . clear ( ) ;
1258+ empty_history. users . clear ( ) ;
1259+ empty_history. files . clear ( ) ;
1260+ applied. apply_history_page ( & empty_history) . unwrap ( ) ;
1261+ applied
1262+ . begin_catch_up ( "2026-06-02T00:00:00Z" . to_string ( ) )
1263+ . unwrap ( ) ;
1264+
1265+ let template = history_page ( ) . messages [ 0 ] . clone ( ) ;
1266+ let roots = ( 0 ..300 )
1267+ . map ( |offset| {
1268+ let mut root = template. clone ( ) ;
1269+ root. message . ts = format ! ( "1780272000.{offset:06}" ) ;
1270+ root. message . thread_ts = None ;
1271+ root. message . user_id = None ;
1272+ root. message . file_ids . clear ( ) ;
1273+ root. reply_count = 0 ;
1274+ root
1275+ } )
1276+ . collect :: < Vec < _ > > ( ) ;
1277+ let mut seed = catch_up_page ( ) ;
1278+ seed. messages . clone_from ( & roots) ;
1279+ applied. apply_history_page ( & seed) . unwrap ( ) ;
1280+ assert ! ( applied. completed_output( ) . is_ok( ) ) ;
1281+
1282+ let mut sweep = HostedSlackPollCheckpointV1 :: incremental_from_applied (
1283+ & applied,
1284+ raw_snapshot ( ) . channel ,
1285+ "2026-06-01T23:55:00Z" . to_string ( ) ,
1286+ )
1287+ . unwrap ( ) ;
1288+ sweep
1289+ . begin_catch_up ( "2026-06-02T00:05:00Z" . to_string ( ) )
1290+ . unwrap ( ) ;
1291+ let mut empty_incremental: HostedSlackHistoryPageV2 = catch_up_page ( ) . into ( ) ;
1292+ empty_incremental. page_format_version = HOSTED_SLACK_POLL_PAGE_FORMAT_VERSION_V3 ;
1293+ empty_incremental. minimum_reader_version = HOSTED_SLACK_POLL_PAGE_MINIMUM_READER_VERSION_V3 ;
1294+ empty_incremental. poll_kind = HostedSlackPollKindV2 :: Incremental ;
1295+ empty_incremental. backfill_cut_at = "2026-06-02T00:00:00Z" . to_string ( ) ;
1296+ empty_incremental. poll_overlap_watermark = "2026-06-01T23:55:00Z" . to_string ( ) ;
1297+ empty_incremental. poll_cut_at = Some ( "2026-06-02T00:05:00Z" . to_string ( ) ) ;
1298+ empty_incremental. observed_at = "2026-06-02T00:05:01Z" . to_string ( ) ;
1299+ sweep. apply_history_page_v2 ( & empty_incremental) . unwrap ( ) ;
1300+
1301+ for ( offset, root) in roots. iter ( ) . enumerate ( ) {
1302+ assert ! (
1303+ sweep. completed_output( ) . is_err( ) ,
1304+ "no false Fresh at {offset}"
1305+ ) ;
1306+ let mut page: HostedSlackRepliesPageV2 = catch_up_replies_page ( replies_page ( ) ) . into ( ) ;
1307+ page. page_format_version = HOSTED_SLACK_POLL_PAGE_FORMAT_VERSION_V3 ;
1308+ page. minimum_reader_version = HOSTED_SLACK_POLL_PAGE_MINIMUM_READER_VERSION_V3 ;
1309+ page. poll_kind = HostedSlackPollKindV2 :: Incremental ;
1310+ page. backfill_cut_at = "2026-06-02T00:00:00Z" . to_string ( ) ;
1311+ page. poll_overlap_watermark = "2026-06-01T23:55:00Z" . to_string ( ) ;
1312+ page. poll_cut_at = Some ( "2026-06-02T00:05:00Z" . to_string ( ) ) ;
1313+ page. request_cursor = None ;
1314+ page. next_cursor = None ;
1315+ page. root_message_id . clone_from ( & root. message . ts ) ;
1316+ page. root_reply_count = u32:: from ( offset == 298 ) ;
1317+ page. observed_at = "2026-06-02T00:05:02Z" . to_string ( ) ;
1318+ page. messages = vec ! [ root. message. clone( ) ] ;
1319+ page. users . clear ( ) ;
1320+ page. files . clear ( ) ;
1321+ if offset == 298 {
1322+ let mut late = root. message . clone ( ) ;
1323+ late. ts = "1780272200.000001" . to_string ( ) ;
1324+ late. thread_ts = Some ( root. message . ts . clone ( ) ) ;
1325+ late. text = "late near sweep end" . to_string ( ) ;
1326+ page. messages . push ( late) ;
1327+ }
1328+
1329+ if offset == 150 {
1330+ let encoded = serde_json:: to_vec ( & sweep) . unwrap ( ) ;
1331+ assert ! ( encoded. len( ) <= MAX_HOSTED_SLACK_CHECKPOINT_BYTES_V1 ) ;
1332+ let mut replay = decode_hosted_slack_poll_checkpoint_v3 ( & encoded) . unwrap ( ) ;
1333+ sweep. apply_replies_page_v2 ( & page) . unwrap ( ) ;
1334+ replay. apply_replies_page_v2 ( & page) . unwrap ( ) ;
1335+ assert_eq ! ( sweep, replay, "interruption/replay must be exact" ) ;
1336+ } else {
1337+ sweep
1338+ . apply_replies_page_v2 ( & page)
1339+ . unwrap_or_else ( |error| panic ! ( "root {offset}: {error:?}" ) ) ;
1340+ }
1341+ }
1342+
1343+ let encoded = serde_json:: to_vec ( & sweep) . unwrap ( ) ;
1344+ assert ! ( encoded. len( ) <= MAX_HOSTED_SLACK_CHECKPOINT_BYTES_V1 ) ;
1345+ assert ! ( decode_hosted_slack_poll_checkpoint_v2( & encoded) . is_err( ) ) ;
1346+ let output = sweep. completed_output ( ) . unwrap ( ) ;
1347+ assert ! ( output. operational_status. coverage_complete) ;
1348+ assert_eq ! (
1349+ output. operational_status. freshness_state,
1350+ locality_protocol:: ReplicaFreshnessState :: Fresh
1351+ ) ;
1352+ assert_eq ! (
1353+ output
1354+ . snapshot
1355+ . messages( )
1356+ . iter( )
1357+ . filter( |message| message. text( ) == "late near sweep end" )
1358+ . count( ) ,
1359+ 1
1360+ ) ;
1361+ }
1362+
12501363#[ test]
12511364fn v1_poll_enums_remain_exhaustive_and_source_compatible ( ) {
12521365 fn kind_name ( kind : HostedSlackPollKindV1 ) -> & ' static str {
0 commit comments