aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-08 09:33:41 +0000
committertv <tv@krebsco.de>2026-11-08 10:36:24 +0100
commitf713fe668c29ecd60df547d989b0ace40cfe4da0 (patch)
tree329d4a0b67be2a214427d7bfe878649b3a4fc916
parentb758c93a1887ea36b0f52ccd4a6f9824de77d819 (diff)
access: a forgotten node cancels its own record
A node forgotten swarm-wide while alive cancels its forget record with a change naming no user; the rules judged _continuum/forgotten by the registry alone, so every node refused the cancellation and everything the node wrote after it. A node may now write its own _continuum/forgotten/<id>, as it may its member entry. Review finding 4.
-rw-r--r--docs/access.md1
-rw-r--r--src/access/rules.rs9
2 files changed, 9 insertions, 1 deletions
diff --git a/docs/access.md b/docs/access.md
index 1ee9994..8443394 100644
--- a/docs/access.md
+++ b/docs/access.md
@@ -224,6 +224,7 @@ revocation since refuses nothing it allowed then, and every node judges alike.
* A remake's decision by the coordinator its proposal names; the rest of a record by
any member.
* A user's tokens revoked by that user too (logging out).
+* A member's forget record cancelled by that member too (forgotten while alive).
* Everything else, protocol data or not, by a user the registry lets write there.
* A node that isn't a member may only register itself; a banned node nothing (Banning
a node, below). So a node registers before anything else it writes, through the API
diff --git a/src/access/rules.rs b/src/access/rules.rs
index 2aed4b8..96ee9d4 100644
--- a/src/access/rules.rs
+++ b/src/access/rules.rs
@@ -15,7 +15,8 @@
//! that member only (removing an entry also by a user who may write there); epochs
//! and the swarm ID in a root only; a remake's decision by the coordinator its
//! proposal names, the rest of a record by any member; a user's tokens revoked by that
-//! user too (logging out); the rest by a user who may write there.
+//! user too (logging out); a member's forget record by that member too (cancelling it,
+//! alive); the rest by a user who may write there.
//! * User data: by a user who may write there.
//! * Under a strict policy (`access::policy`), the values their users sign
//! (`access::envelope`) only as they signed them (`signed_values`).
@@ -134,6 +135,8 @@ pub(crate) fn judge(doc: &Document, epoch: Epoch, change: &Change<'_>) -> Result
(Some(meta::EPOCHS | meta::SWARM), _) => false,
// Logging out: a user revokes its own tokens (`POST /auth/logout`).
(Some(meta::LOGOUTS), Some(owner)) => change.user == Some(owner) || user_may(path),
+ // A node forgotten while alive cancels its own record (`swarm::peer::forget`).
+ (Some(meta::FORGOTTEN), Some(owner)) => owner == node || user_may(path),
(Some(meta::REMAKES), Some(id)) => match path.get(3).map(String::as_str) {
// Its coordinator's; or a record carried into a new epoch whole, by the
// node that coordinated it (`remake::switch`): its proposal written with it.
@@ -431,6 +434,10 @@ mod tests {
let tokens = "_continuum/logouts/bob";
assert!(judged(&doc, "a", Some("bob"), &[tokens]).is_ok());
assert!(refused(judged(&doc, "a", Some("alice"), &[tokens])));
+ // A forget record: cancelled by the node it names, not by another.
+ let forgotten = "_continuum/forgotten/a";
+ assert!(judged(&doc, "a", None, &[forgotten]).is_ok());
+ assert!(refused(judged(&doc, "b", None, &[forgotten])));
}
/// A banned node's changes: refused, as the ban stood when they were made.