aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-08 18:12:59 +0000
committertv <tv@krebsco.de>2026-11-08 19:28:43 +0100
commitb1b04ed174ed696a4c3c7988f3640b997a54c7e2 (patch)
treed89c49a94e689c586040eb847809d9da369fce86
parent5bcf27ca2a10f16befb96c70bf04ae8a5c04ceaa (diff)
screen: maps made, and votes, judged with what came before them in
Review 2, N6: finding 2's class survived for two reads the frontier didn't cover: whether a map a change makes is there (maps made on the way, 1137; holders made where none, 1147, for any path), and _continuum/votes, which `relevant` didn't list. A crafted pair, the second making a map the first made, was taken by nodes receiving both in one batch (judged at the frontier, where the map isn't yet) and refused by nodes receiving them apart. The screen now keeps the paths the accepted, not applied yet, touch; a change making a map at one of them is judged after they go in. Votes join what `relevant` lists. A crafted pair needs hand-made changes, which the tests don't build.
-rw-r--r--src/access/screen.rs38
1 files changed, 30 insertions, 8 deletions
diff --git a/src/access/screen.rs b/src/access/screen.rs
index 89ca007..5584b2b 100644
--- a/src/access/screen.rs
+++ b/src/access/screen.rs
@@ -4,9 +4,11 @@
//!
//! A change is judged at the heads its writer saw. Those of a batch aren't in the
//! document yet, so one is judged at the changes of the document its batch ancestors
-//! build on: what rules read there (the members, the user registry, remake proposals,
-//! the policy, bans) is what its writer saw, unless one of those ancestors changed it. So a change that
-//! does (`relevant`) is applied before the next is judged: the rest go in at once.
+//! build on: what rules read there (the members, the user registry, remake records,
+//! votes, the policy, bans, whether a map it makes is there) is what its writer saw,
+//! unless one of those ancestors changed it. So a change that does (`relevant`), and
+//! those before a change making a map where they wrote, are applied before the next is
+//! judged: the rest go in at once.
//!
//! A member whose own change broke a rule, as its signature proves (signatures
//! required), is refused as a whole (`Screening::refusing`): the node sends it nothing
@@ -34,7 +36,7 @@ use std::collections::{BTreeSet, HashMap, HashSet};
use anyhow::Result;
use super::rules::{self, Refusal};
-use crate::document::inspect::{Inspector, Op, Path};
+use crate::document::inspect::{Action, Inspector, Op, Path};
use crate::document::meta;
use crate::document::replica::{Epoch, user_of};
use crate::document::signatures::Signatures;
@@ -348,6 +350,9 @@ pub(crate) fn apply(
let mut unsigned: HashSet<ChangeHash> = HashSet::new();
let mut forged: HashSet<ChangeHash> = HashSet::new();
let mut accepted: Vec<Change> = Vec::new();
+ // What the accepted, not applied yet, touch: a change making a map at one of these
+ // paths is judged with them in (`rules`' maps made where there are none).
+ let mut unapplied: BTreeSet<Path> = BTreeSet::new();
for (index, change) in ready.iter().enumerate() {
let hash = change.hash();
let committer = committers[index].as_deref();
@@ -404,6 +409,18 @@ pub(crate) fn apply(
waiting.0.push(change.clone());
continue;
}
+ // It makes a map where one accepted before may have: the rules read whether one
+ // is there, so those go in first.
+ if !accepted.is_empty()
+ && ops[index]
+ .iter()
+ .any(|op| op.action == Action::MakeMap && unapplied.contains(&op.path))
+ {
+ screened.applied += accepted.len();
+ apply_taken(doc, std::mem::take(&mut accepted), log)?;
+ frontier.clear();
+ unapplied.clear();
+ }
let mut seen_at: BTreeSet<ChangeHash> = BTreeSet::new();
for dep in change.deps() {
match frontier.get(dep) {
@@ -439,8 +456,10 @@ pub(crate) fn apply(
screened.applied += accepted.len();
apply_taken(doc, std::mem::take(&mut accepted), log)?;
frontier.clear();
+ unapplied.clear();
} else {
frontier.insert(hash, seen_at);
+ unapplied.extend(paths[index].iter().cloned());
}
}
Err(Refusal::Unready) => {
@@ -626,10 +645,12 @@ pub(crate) fn apply_bytes(
}
/// Whether a change touching `paths` changes what rules read: the members, the user
-/// registry, remake records, the policy and the rest of `_continuum/access`, bans, the
-/// protocol data as a whole; and, under a strict policy, values their users sign
-/// (`reaches_signed`). A rule that reads anything else (`rules::judge`) must add it
-/// here, or a batch judged at its frontier is judged at heads it doesn't hold.
+/// registry, remake records, votes, the policy and the rest of `_continuum/access`,
+/// bans, the protocol data as a whole; under a strict policy, values their users sign
+/// (`reaches_signed`); and, for a change making a map, whether one is there, which
+/// `apply` checks against what the accepted before it touch. A rule that reads anything
+/// else (`rules::judge`) must add it here, or a batch judged at its frontier is judged
+/// at heads it doesn't hold.
fn relevant(paths: &BTreeSet<Path>) -> bool {
paths.iter().any(|path| match path.as_slice() {
[] => true,
@@ -639,6 +660,7 @@ fn relevant(paths: &BTreeSet<Path>) -> bool {
meta::MEMBERS,
meta::USERS,
meta::REMAKES,
+ meta::VOTES,
meta::ACCESS,
meta::BANNED,
]