From d7eaef8b94c13fb2da4c87e98dd2464a31b5c128 Mon Sep 17 00:00:00 2001 From: tv Date: Mon, 9 Nov 2026 15:27:36 +0000 Subject: remake: earlier epochs' metadata carried as built, not as it stands Review 4, F6: a snapshot judged which earlier epochs' metadata to leave out as the base epoch's snapshot built it (1254), but copied `_continuum/epochs` from the base as it stood. In a swarm without a policy, a member's rewrite of an entry (its window, kind, clock, translation) went into the next epoch, and was "as built" there; an entry it added was carried and never left out, one it removed not carried. Now `_continuum/epochs` is copied from the base epoch's snapshot change too (`as_built`), then pruned; the first epoch's base carries none. The test adds a generation and narrows a window after the snapshot: neither reaches the next one. --- src/remake/snapshot.rs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) (limited to 'src/remake') diff --git a/src/remake/snapshot.rs b/src/remake/snapshot.rs index 2bb5fbb..b4f1f35 100644 --- a/src/remake/snapshot.rs +++ b/src/remake/snapshot.rs @@ -384,12 +384,19 @@ pub(crate) fn build( protocol_keys.sort(); for key in protocol_keys .into_iter() - .filter(|key| key != REMAKES && key != VOTES) + .filter(|key| key != REMAKES && key != VOTES && key != EPOCHS) { store::copy_path(&base, &mut target, &meta::path(&[key.as_str()]))?; } - // As the base's epoch built them, whatever was written there since. + // Earlier epochs' metadata as the base's epoch built it, whatever was written there + // since (with no policy, a member's own change could): copied from there, and judged + // there which to leave out. The first epoch follows none and carries none. let built = as_built(&base, params.previous).map_err(SnapshotError::Earlier)?; + if let Some(built) = &built + && store::keys_at(built, &meta::path(&[EPOCHS]))?.is_some() + { + store::copy_path(built, &mut target, &meta::path(&[EPOCHS]))?; + } let earlier: BTreeMap> = match &built { Some(built) => store::keys_at(built, &meta::path(&[EPOCHS]))? .unwrap_or_default() @@ -816,8 +823,9 @@ mod tests { .unwrap_or_default() }; - // A change since the snapshot narrowing generation 1's window changes nothing: - // the metadata is read as the snapshot built it. + // A change since the snapshot narrowing generation 1's window, or adding a + // generation, changes nothing: the metadata is read and carried as the snapshot + // built it. let mut edited = first .doc .fork() @@ -829,14 +837,24 @@ mod tests { &json!({ "replayWindow": { "epochs": 0 } }), ) .unwrap(); + store::write_path(d, &meta::path(&[EPOCHS, "99"]), &json!({})).unwrap(); }); let heads = edited.get_heads(); let mut second = params(&heads, &policy); second.remake_id = "c-2"; second.previous = first.epoch; let second = build(&edited, &second).unwrap(); - // A node on genesis is two behind generation 2, within the window: 1 stays. + // A node on genesis is two behind generation 2, within the window: 1 stays, as + // built. assert_eq!(generations(&second.doc), vec!["1", "2"]); + assert_eq!( + read_epoch_metadata(&second.doc, 1) + .unwrap() + .policy + .replay_window + .epochs, + Some(2) + ); let heads = second.doc.get_heads(); let mut third = params(&heads, &policy); -- cgit v1.3.1