Overview:
src/filter.rs, add a new variant to
enum EventFilter, with whatever fields are needed for that
filtersrc/filter.rs, in the
EventFilter::decide method, add a new match
arm for the new filter variantci-broker.md add a new section
## Filter predicte XXX, like the existing ones, to verify
the new filter worksmake at the root of the source tree to make sure
everything worksThe patch to add a AnyDelegate filter looks like
below.
commit 94fbd9f72fc4db779422fa97786c99bb9b3b14d4
Author: Lars Wirzenius <liw@liw.fi>
Date: Mon Nov 10 14:00:53 2025 +0200
feat: add the AnyDelegate filter
diff --git a/ci-broker.md b/ci-broker.md
index f86748e..ac217d0 100644
--- a/ci-broker.md
+++ b/ci-broker.md
@@ -1234,6 +1234,43 @@ sed -i "s/NODEID/$rid/g" "$yaml"
~~~
+## Filter predicate `AnyDelegate`
+
+_Want:_ We can allow an event that originates in a node for any delegate.
+
+_Why:_ We want to constrain CI to privileged developers for a repository.
+
+~~~scenario
+given a Radicle node, with CI configured with broker.yaml and adapter dummy.sh
+given a Git repository xyzzy in the Radicle node
+given a Git repository other in the Radicle node
+
+given file config.yaml from filter-anydelegate.yaml
+given file update-nodeid.sh
+when I run bash update-nodeid.sh xyzzy config.yaml
+
+when I run cibtool --db ci-broker.db trigger --repo xyzzy --commit HEAD
+when I run cibtool --db ci-broker.db trigger --repo other --commit HEAD --node z6MkgEMYod7Hxfy9qCvDv5hYHkZ4ciWmLFgfvm3Wn1b2w2FV
+
+when I run ./env.sh cib --config config.yaml queued
+
+when I run cibtool --db ci-broker.db run list --json
+then stdout contains ""repo_name": "xyzzy""
+then stdout doesn't contain ""repo_name": "other""
+~~~
+
+~~~{#filter-anydelegate.yaml .file .json}
+db: ci-broker.db
+adapters:
+ default:
+ command: ./adapter.sh
+triggers:
+ - adapter: default
+ filters:
+ - !AnyDelegate
+~~~
+
+
## Filter predicate `Tag`
_Want:_ We can allow an event that is about a specific tag.
diff --git a/src/filter.rs b/src/filter.rs
index 188c8bd..86ea648 100644
--- a/src/filter.rs
+++ b/src/filter.rs
@@ -1,5 +1,6 @@
use std::path::{Path, PathBuf};
+use radicle_crypto::PublicKey;
use regex::Regex;
use serde::{Deserialize, Serialize};
@@ -8,7 +9,7 @@ use radicle::{
git::{BranchName, Oid, raw::ObjectType},
node::NodeId,
prelude::{Profile, RepoId},
- storage::git::Repository,
+ storage::{ReadRepository, git::Repository},
};
use crate::{
@@ -92,6 +93,11 @@ pub enum EventFilter {
/// Change originated from specific node.
Node(NodeId),
+ /// Change originated from any delegate node. Note that will change to
+ /// "from delegate" once Radicle separates the "user" and "node"
+ /// concepts.
+ AnyDelegate,
+
/// Commit in change contains a file or directory with this name.
HasFile(PathBuf),
@@ -147,6 +153,25 @@ impl EventFilter {
format!("wanted={wanted} actual={actual:?}"),
)
}
+ #[allow(clippy::unwrap_used)]
+ Self::AnyDelegate => {
+ let repo_id = event.repository().unwrap();
+ let radicle = crate::ergo::Radicle::new().unwrap();
+ let repo = radicle.repository(repo_id).unwrap();
+ let origin = event.from_node().unwrap();
+ let delegates: Vec<PublicKey> = repo
+ .delegates()
+ .iter()
+ .flatten()
+ .map(|d| *d.as_key())
+ .collect();
+ let allowed = delegates.contains(origin);
+ Decision::string(
+ "AnyDelegate",
+ allowed,
+ format!("wanted={origin} delegates={delegates:?}",),
+ )
+ }
Self::Repository(wanted) => {
let actual = event.repository();
let allowed = Some(wanted) == actual;