Skip to content

Commit 3ebd5c0

Browse files
mc-store: keep the materialized boundary when a reconnecting host echoes it
A host without a whole-message boundary of its own sends back the boundary the module reported in its inventory. After compartments are published but not yet folded, that boundary lags the newest compartment, and the seed was refused with state_sync_seed_boundary_mismatch. An echo that is still a real compartment end asks for no change, so the module keeps its boundary; an echo whose compartment the host rewrote still falls through to validation and is refused. Co-authored-by: Alfonso <alfonso-magic-context@users.noreply.github.com>
1 parent 24659ec commit 3ebd5c0

2 files changed

Lines changed: 73 additions & 1 deletion

File tree

‎crates/mc-store/src/context_boundaries.rs‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,55 @@ mod tests {
301301
fn seed(store: &McStore) {
302302
store.with_context_conn_for_test(|conn| conn.execute_batch("INSERT INTO compartments(session_id, sequence, start_message, end_message, start_message_id, end_message_id, title, content, created_at) VALUES ('raw', 0, 2, 5, 'm1', 'm4', 'summary', 'body', 1)")).unwrap();
303303
}
304+
#[test]
305+
fn echoed_materialized_boundary_is_retained_when_a_newer_compartment_exists() {
306+
let dir = tempfile::tempdir().unwrap();
307+
let store = McStore::open_for_test(&descriptor(dir.path())).unwrap();
308+
seed(&store);
309+
let first = [boundary()];
310+
store
311+
.apply_authority_state_sync(request(&first, 0))
312+
.unwrap();
313+
let (meta, materialized, _) = store.load_state_sync_inventory("raw", true).unwrap();
314+
assert!(meta.initialized);
315+
assert_eq!(materialized, "m4#0");
316+
317+
// A newer compartment is published but not folded yet; the host echoes the
318+
// module's own boundary, which now lags the newest compartment.
319+
store
320+
.with_context_conn_for_test(|conn| conn.execute_batch(
321+
"INSERT INTO compartments(session_id, sequence, start_message, end_message, start_message_id, end_message_id, title, content, created_at) VALUES ('raw', 1, 6, 9, 'm5', 'm8', 'later', 'body', 2)",
322+
))
323+
.unwrap();
324+
let mut newer = boundary();
325+
newer.sequence = 1;
326+
newer.source_start_message = 6;
327+
newer.source_end_message = 9;
328+
newer.source_start_message_id = "m5".into();
329+
newer.source_end_message_id = "m8".into();
330+
newer.start_message = 5;
331+
newer.end_message = 8;
332+
newer.start_message_id = "m5#0".into();
333+
newer.end_message_id = "m8#0".into();
334+
let rows = [boundary(), newer];
335+
let (meta, _, _) = store.load_state_sync_inventory("raw", true).unwrap();
336+
store
337+
.apply_authority_state_sync(request(&rows, meta.shadow_seq))
338+
.unwrap();
339+
let (_, retained, _) = store.load_state_sync_inventory("raw", true).unwrap();
340+
assert_eq!(retained, "m4#0");
341+
342+
// A declared boundary that is neither the materialized one nor the newest
343+
// compartment is still refused.
344+
let (meta, _, _) = store.load_state_sync_inventory("raw", true).unwrap();
345+
let mut stale = request(&rows, meta.shadow_seq);
346+
stale.seed_boundary_id = Some("m1#0");
347+
assert!(matches!(
348+
store.apply_authority_state_sync(stale),
349+
Err(crate::ModuleStateSyncError::InvalidSeedBoundary { .. })
350+
));
351+
}
352+
304353
#[test]
305354
fn empty_legacy_source_id_accepts_host_resolution_without_rewriting_shared_row() {
306355
let dir = tempfile::tempdir().unwrap();

‎crates/mc-store/src/lib.rs‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10854,7 +10854,30 @@ impl McStore {
1085410854
if !request.resolved_compartment_boundaries.is_empty() {
1085510855
meta.resolved_compartment_boundaries = resolved_boundaries.clone();
1085610856
}
10857-
if let Some(declared) = request.seed_boundary_id {
10857+
// A reconnecting host without a whole-message boundary of its own echoes the
10858+
// boundary this module reported in its inventory. Compartments published after
10859+
// that fold can already be in context.db, so the echo may lag the newest
10860+
// compartment; it asks for no change, so the materialized boundary is kept
10861+
// rather than validated against the newest compartment and refused. It must
10862+
// still be a real compartment end: if the host rewrote that compartment, the
10863+
// echo falls through to validation and is refused.
10864+
let echoes_materialized_boundary = meta.initialized
10865+
&& request.seed_boundary_id.is_some_and(|declared| {
10866+
declared == core.boundary_id
10867+
&& seed_compartments
10868+
.iter()
10869+
.any(|compartment| compartment.end_message_id == declared)
10870+
});
10871+
if echoes_materialized_boundary {
10872+
tracing::info!(
10873+
"mc-store: state-sync seed echoed materialized boundary {:?}; retained for session {}",
10874+
core.boundary_id, request.session_id
10875+
);
10876+
}
10877+
if let Some(declared) = request
10878+
.seed_boundary_id
10879+
.filter(|_| !echoes_materialized_boundary)
10880+
{
1085810881
let adoption = match validated_seed_boundary(declared, &seed_compartments) {
1085910882
Ok(adoption) => adoption,
1086010883
Err(detail) => {

0 commit comments

Comments
 (0)