aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-07 22:35:22 +0100
committertv <tv@krebsco.de>2026-11-07 22:35:22 +0100
commitfa695849c3b854670df7642b91a7937316e32b5b (patch)
treee934c093d69e9a9317b425c8dfd7a198b618566d /src
parent56a3dca1874fba17c3d174be451af9478b3bfb9d (diff)
simtest: members against the rules, and a policy unknown
A member writing past its API's checks is refused, told, drops its change and is taken again; a node under a policy it doesn't know takes no writes and syncs on. Auth plan M6 (tests).
Diffstat (limited to 'src')
-rw-r--r--src/simtest/policy.rs102
1 files changed, 102 insertions, 0 deletions
diff --git a/src/simtest/policy.rs b/src/simtest/policy.rs
index 5ad05fd..68f394f 100644
--- a/src/simtest/policy.rs
+++ b/src/simtest/policy.rs
@@ -478,3 +478,105 @@ async fn a_change_taken_unjudged() {
assert!(!holds(node, "notes/after").await);
}
}
+
+/// A member writing against the rules (past its API's checks): refused by every other
+/// node (as a whole, its own change being signed, for as long as it takes); told which
+/// change broke the rules (REFUSALS), it judges it itself, drops it, its write
+/// quarantined, and is taken again with what it writes next.
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+async fn a_member_writing_against_the_rules() {
+ let sim = Sim::with_identity(76);
+ let a = sim.start_with("a", &[], Start::Genesis, |config| {
+ config.first_users = [("alice".to_owned(), Default::default())].into();
+ });
+ let b = sim.start("b", &["a"], Start::Join);
+ let c = sim.start("c", &["a"], Start::Join);
+ sim.wait_ready(&[&a, &b, &c], SOON).await;
+ let nodes = [a.clone(), b.clone(), c.clone()];
+ sim.wait_until("every node is registered", SOON, || {
+ let nodes = nodes.clone();
+ async move {
+ for node in &nodes {
+ if crate::swarm::members::members(&node.doc.read().await.doc).len() < 3 {
+ return false;
+ }
+ }
+ true
+ }
+ })
+ .await;
+ write_as(
+ &a,
+ "alice",
+ "_continuum/access/policy",
+ json!({"version": 1, "signatures": "required"}),
+ )
+ .await;
+ sim.wait_converged(&[&a, &b, &c], SOON).await;
+
+ write_as(&c, "mallory", "notes/forged", json!(1)).await;
+ let rogue = c.local_id().to_owned();
+
+ // By whichever node judges it first: the other may still be waiting for its
+ // signature when c drops it, and then never gets to judge it.
+ sim.wait_until("the change is refused", SOON, || async {
+ refused(&a).await + refused(&b).await > 0
+ })
+ .await;
+ sim.wait_until("c drops its change", SOON, || async {
+ !holds(&c, "notes/forged").await
+ })
+ .await;
+ assert_eq!(c.remake.list_quarantine(|entry| entry.id.clone()).len(), 1);
+ write_as(&c, "alice", "notes/honest", json!(2)).await;
+ for node in [&a, &b] {
+ let rogue = rogue.clone();
+ sim.wait_until("c's next write is taken, and c with it", SOON, move || {
+ let rogue = rogue.clone();
+ async move {
+ use crate::locks::MutexExt;
+ holds(node, "notes/honest").await
+ && node.screening.lock_or_recover().refusing(&rogue).is_none()
+ }
+ })
+ .await;
+ assert!(!holds(node, "notes/forged").await);
+ }
+}
+
+/// A policy this node doesn't know (another version): it takes no writes through the
+/// API, as it can't tell what the others would refuse, and syncs on.
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+async fn a_policy_this_node_doesnt_know() {
+ let sim = Sim::with_identity(77);
+ let a = sim.start_with("a", &[], Start::Genesis, |config| {
+ config.first_users = [("alice".to_owned(), Default::default())].into();
+ });
+ let b = sim.start("b", &["a"], Start::Join);
+ sim.wait_ready(&[&a, &b], SOON).await;
+ write_as(&a, "alice", "notes/before", json!(1)).await;
+ write_as(
+ &a,
+ "alice",
+ "_continuum/access/policy",
+ json!({"version": 2}),
+ )
+ .await;
+ sim.wait_converged(&[&a, &b], SOON).await;
+ assert!(holds(&b, "notes/before").await);
+ let at = path("notes/after");
+ let refused = b
+ .mutate_doc_for(
+ Some("alice".to_owned()),
+ Sign::Write {
+ path: at.clone(),
+ asked: false,
+ },
+ move |doc| store::write_path(doc, &at, &json!(2)),
+ )
+ .await;
+ assert!(
+ matches!(refused, Err(store::StoreError::UnknownPolicy(_))),
+ "a write under a policy this node doesn't know"
+ );
+}