From 96ba15cf1a6f8725f70f429695a5a9661f706eb0 Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 8 Nov 2026 18:17:14 +0000 Subject: strict: a manifest serves one remake, at its base Review 2, N1: a strict manifest wasn't bound to its base. Any member could propose an old, legitimately signed one again at the current heads; nodes would verify it and vote, and every value at a signed place changed or added since (a ban, a new user, a rotated key, a changed userSigned or sealed declaration) would be deleted, nothing after the base being left to replay. The manifest now names the epoch and the heads it was made at, and a node takes it (proposing or voting) only for a remake of that epoch with exactly that base. GET /admin/strict-manifest makes it so; continuum-signed strict proposes at those heads already. --- src/remake/api.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'src/remake/api.rs') diff --git a/src/remake/api.rs b/src/remake/api.rs index 79b551f..7aeb675 100644 --- a/src/remake/api.rs +++ b/src/remake/api.rs @@ -393,7 +393,13 @@ async fn start_kind( .unwrap_or_default() }) .await; - if let Err(why) = super::vote::strict_taken(&host, prepared.content.as_deref(), &swarm) { + if let Err(why) = super::vote::strict_taken( + &host, + prepared.content.as_deref(), + &swarm, + &EpochRecord::from(host.current_epoch()), + request.base_heads.as_deref(), + ) { return error_response( StatusCode::FORBIDDEN, format!("this node takes no such manifest: {why}"), @@ -817,6 +823,7 @@ pub(crate) async fn strict_manifest( ) -> Response { let at = host.now_millis().as_u64(); let caller = user.0.clone(); + let epoch = EpochRecord::from(host.current_epoch()); let (manifest, values, heads) = host .read_doc(move |doc| { let read = |path: &[&str]| { @@ -832,12 +839,14 @@ pub(crate) async fn strict_manifest( _ => json!({"version": 1}), }; policy["values"] = json!("signed"); - let manifest = crate::access::strict::manifest_of(doc, &swarm, policy, &caller, at); + let heads = doc.get_heads(); + let manifest = + crate::access::strict::manifest_of(doc, &swarm, epoch, &heads, policy, &caller, at); let values: Vec = crate::access::strict::held(doc) .into_iter() .map(|(path, value)| json!({ "path": path, "value": value })) .collect(); - let heads = records::encode_heads(&doc.get_heads()); + let heads = records::encode_heads(&heads); (manifest, values, heads) }) .await; -- cgit v1.3.1