engine: services-operator privilege foundation

A typed Priv enum (Auspex/Suspend/Admin) and a Copy Privs bitset carried on
Sender, instead of Anope's stringly-typed privilege namespace and its two
separate gating mechanisms. Opers are declared in TOML ([[oper]] account +
privs); the engine loads them and resolves the sender's privileges per command,
so a module just checks from.privs.has(Priv::X). First use: NickServ INFO
reveals hidden fields to an auspex oper. Unblocks SUSPEND, admin LIST, SASET,
GETEMAIL. Priv bitset test + end-to-end auspex engine test.
This commit is contained in:
Jean Chevronnet 2026-07-13 03:42:59 +00:00
parent 4600ee062c
commit f1415bedb2
No known key found for this signature in database
6 changed files with 139 additions and 6 deletions

View file

@ -19,6 +19,38 @@ pub struct Config {
// Which service modules to start. Absent = the built-in NickServ + ChanServ.
#[serde(default)]
pub modules: Modules,
// Services operators: accounts granted privileges. Absent = no opers.
#[serde(default)]
pub oper: Vec<Oper>,
}
// One services operator: an account and the privileges it holds.
#[derive(Debug, Deserialize, Clone)]
pub struct Oper {
pub account: String,
#[serde(default)]
pub privs: Vec<String>, // "auspex" | "suspend" | "admin"
}
impl Config {
// The account -> privileges table (casefolded keys) built from [[oper]].
pub fn opers(&self) -> std::collections::HashMap<String, fedserv_api::Privs> {
use fedserv_api::{Priv, Privs};
let mut map = std::collections::HashMap::new();
for o in &self.oper {
let mut privs = Privs::default();
for p in &o.privs {
match p.to_ascii_lowercase().as_str() {
"auspex" => privs = privs.with(Priv::Auspex),
"suspend" => privs = privs.with(Priv::Suspend),
"admin" => privs = privs.with(Priv::Admin),
other => tracing::warn!(privilege = other, account = %o.account, "unknown oper privilege, ignoring"),
}
}
map.insert(o.account.to_ascii_lowercase(), privs);
}
map
}
}
// The service modules to bring up at burst. Each name maps to a compiled-in