Re: Sashiko email policy precedence

"Danilo Krummrich" <[email protected]> Mon, 01 Jun 2026 11:46:54 +0200
Newsgroups dev.linux.lists.sashiko
Message-ID <[email protected]>
On Mon Jun 1, 2026 at 8:16 AM CEST, Miguel Ojeda wrote:
> On Mon, Jun 1, 2026 at 1:17=E2=80=AFAM Danilo Krummrich <[email protected]>=
 wrote:
> IIUC, from what Danilo says, the current algorithm's conservative approac=
h
> would remove that setting as soon as the patch is sent to any list where =
that
> isn't the case, right?

Only if the other list has a sashiko email policy entry in the first place,
otherwise it is not part of the matching loop and no further logic is appli=
ed.

I'd assume that this is intentional, and can be read as "no entry, no expre=
ssed
preference".

> if it is too complex to implement other logic, I guess it is an option.

Flagging something as wide list seems rather simple [1], the tristate [2] w=
ould
work as well, but seems a bit more complicated and potentially requires
adjusting the existing policies.

(Test adjustments stripped from [2]; code is LLM generated.)

[1]

diff --git a/src/email_policy.rs b/src/email_policy.rs
index 9fe1179330f3..1ae892e67535 100644
--- a/src/email_policy.rs
+++ b/src/email_policy.rs
@@ -43,6 +43,10 @@ pub struct SubsystemPolicy {
     pub embargo_hours: Option<u32>,
     #[serde(default)]
     pub send_positive_review: bool,
+    /// When true and co-matched with a non-wide list, only `cc` is
+    /// contributed. All boolean flags are deferred to the non-wide lists.
+    #[serde(default)]
+    pub wide_list: bool,
 }

 impl EmailPolicyConfig {
diff --git a/src/email_router.rs b/src/email_router.rs
index c17273aad8ff..216a85d7738d 100644
--- a/src/email_router.rs
+++ b/src/email_router.rs
@@ -93,7 +93,18 @@ impl EmailRouter {
         let mut send_positive_review =3D false;
         let mut cc =3D Vec::new();

-        for p in active_policies {
+        let has_non_wide =3D active_policies.iter().any(|p| !p.wide_list);
+
+        for p in &active_policies {
+            let deferred =3D p.wide_list && has_non_wide;
+
+            if deferred {
+                for cr in &p.cc {
+                    cc.push(cr.clone());
+                }
+                continue;
+            }
+
             if p.mute_all {
                 mute_all =3D true;
             }

[2]

diff --git a/src/email_policy.rs b/src/email_policy.rs
index 9fe1179330f3..5c9d94a4d416 100644
--- a/src/email_policy.rs
+++ b/src/email_policy.rs
@@ -23,14 +23,15 @@ pub struct EmailPolicyConfig {
 pub struct SubsystemPolicy {
     #[serde(default)]
     pub lists: Vec<String>,
+    /// None =3D no preference, Some(false) =3D private, Some(true) =3D pu=
blic.
     #[serde(default)]
-    pub reply_all: bool,
+    pub reply_all: Option<bool>,
     #[serde(default)]
-    pub reply_to_author: bool,
+    pub reply_to_author: Option<bool>,
     #[serde(default)]
-    pub cc_individuals: bool,
+    pub cc_individuals: Option<bool>,
     #[serde(default)]
-    pub mute_all: bool,
+    pub mute_all: Option<bool>,
     #[serde(default)]
     pub cc: Vec<String>,
     #[serde(default)]
@@ -42,7 +43,7 @@ pub struct SubsystemPolicy {
     #[serde(default)]
     pub embargo_hours: Option<u32>,
     #[serde(default)]
-    pub send_positive_review: bool,
+    pub send_positive_review: Option<bool>,
 }
=20
 impl EmailPolicyConfig {
diff --git a/src/email_router.rs b/src/email_router.rs
index c17273aad8ff..452c09411552 100644
--- a/src/email_router.rs
+++ b/src/email_router.rs
@@ -12,6 +12,19 @@ pub enum Action {
=20
 pub struct EmailRouter {}
=20
+/// Merges two Option<bool> policy votes. `dominant` is the value that win=
s on
+/// conflict: `true` for mute_all, reply_to_author, cc_individuals, and
+/// send_positive_review (any-true wins); `false` for reply_all (any-false=
 wins).
+fn merge_flag(acc: Option<bool>, vote: Option<bool>, dominant: bool) -> Op=
tion<bool> {
+    match (acc, vote) {
+        (_, None) =3D> acc,
+        (None, _) =3D> vote,
+        (Some(v), _) if v =3D=3D dominant =3D> acc,
+        (_, Some(v)) if v =3D=3D dominant =3D> vote,
+        _ =3D> acc,
+    }
+}
+
 impl EmailRouter {
     pub fn resolve_patchwork(
         policy: &EmailPolicyConfig,
@@ -86,34 +99,30 @@ impl EmailRouter {
             active_policies.push(&policy.defaults);
         }
=20
-        let mut mute_all =3D false;
-        let mut is_private =3D false;
-        let mut reply_to_author =3D false;
-        let mut cc_individuals =3D false;
-        let mut send_positive_review =3D false;
+        let mut mute_all =3D None;
+        let mut reply_all =3D None;
+        let mut reply_to_author =3D None;
+        let mut cc_individuals =3D None;
+        let mut send_positive_review =3D None;
         let mut cc =3D Vec::new();
=20
         for p in active_policies {
-            if p.mute_all {
-                mute_all =3D true;
-            }
-            if !p.reply_all {
-                is_private =3D true;
-            }
-            if p.reply_to_author {
-                reply_to_author =3D true;
-            }
-            if p.cc_individuals {
-                cc_individuals =3D true;
-            }
-            if p.send_positive_review {
-                send_positive_review =3D true;
-            }
+            mute_all =3D merge_flag(mute_all, p.mute_all, true);
+            reply_all =3D merge_flag(reply_all, p.reply_all, false);
+            reply_to_author =3D merge_flag(reply_to_author, p.reply_to_aut=
hor, true);
+            cc_individuals =3D merge_flag(cc_individuals, p.cc_individuals=
, true);
+            send_positive_review =3D merge_flag(send_positive_review, p.se=
nd_positive_review, true);
             for cr in &p.cc {
                 cc.push(cr.clone());
             }
         }
=20
+        let mute_all =3D mute_all.unwrap_or(false);
+        let is_private =3D !reply_all.unwrap_or(false);
+        let reply_to_author =3D reply_to_author.unwrap_or(false);
+        let cc_individuals =3D cc_individuals.unwrap_or(false);
+        let send_positive_review =3D send_positive_review.unwrap_or(false)=
;
+
         // Always append defaults.cc so users can define a global CC
         for cr in &policy.defaults.cc {
             cc.push(cr.clone());