diff options
| author | tv <tv@krebsco.de> | 2026-11-08 18:12:59 +0000 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-08 19:28:43 +0100 |
| commit | b1b04ed174ed696a4c3c7988f3640b997a54c7e2 (patch) | |
| tree | d89c49a94e689c586040eb847809d9da369fce86 | |
| parent | 5bcf27ca2a10f16befb96c70bf04ae8a5c04ceaa (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.rs | 38 |
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, ] |
