Watch
0
0
Fork
You've already forked hyperhive
0

archetype flows behave like the built-ins: switch off per archetype or per agent, board cards, duplicate

The document's disabled list takes archetype:<archetype>/<flow> (off for every member) and the same with @<agent> (off for one member); a key naming nothing is ignored. GET /api/notify/flows lists the archetype flows with their members, the board draws a card for each with a switch, a checkbox per agent and duplicate, and the dry run counts their hits. The flows keep following the archetype.
This commit is contained in:
atlas 2026-11-11 19:08:19 +01:00 • committed by mara
commit 68c24643f6

View file

@ -27,6 +27,41 @@ pub const BUCKET: &str = "notify-flows";
/// The one key in [`BUCKET`].
pub const KEY: &str = "all";
/// The key an archetype's flow `flow` of `archetype` goes by: in the
/// document's `disabled_builtins` it switches the flow off for every member,
/// and it names the flow in the router's log, a todo's source and the hit
/// counts. `flow` is the `id` the archetype declares.
#[must_use]
pub fn archetype_flow_key(archetype: &str, flow: &str) -> String {
format!("archetype:{archetype}/{flow}")
}
/// [`archetype_flow_key`] followed by `@agent`: in `disabled_builtins` it
/// switches the flow off for that one member.
#[must_use]
pub fn archetype_member_key(archetype: &str, flow: &str, agent: &str) -> String {
format!("{}@{agent}", archetype_flow_key(archetype, flow))
}
/// Whether `key` has the shape of [`archetype_flow_key`] or
/// [`archetype_member_key`]. Whether such a flow, or agent, exists is not
/// asked: a key for one that is gone is ignored, so editing an archetype
/// cannot make the stored document fail its check.
#[must_use]
pub fn is_archetype_key(key: &str) -> bool {
let Some(rest) = key.strip_prefix("archetype:") else {
return false;
};
let Some((archetype, rest)) = rest.split_once('/') else {
return false;
};
let (flow, agent) = match rest.split_once('@') {
Some((flow, agent)) => (flow, Some(agent)),
None => (rest, None),
};
is_id(archetype) && is_id(flow) && agent.is_none_or(is_id)
}
/// The document layout this build reads and writes. Version 1 wrote a todo's
/// key and text with `{field}` placeholders; version 2 writes them as Jinja
/// templates (`{{ field }}`). [`Document::decode`] converts a version 1
@ -158,7 +193,8 @@ pub struct Document {
/// leaves the document alone.
#[serde(default = "version")]
pub version: u32,
/// The ids of the built-in flows that are switched off.
/// The ids of the built-in flows that are switched off, and the keys of the
/// archetype flows that are ([`archetype_flow_key`], [`archetype_member_key`]).
#[serde(default)]
pub disabled_builtins: Vec<String>,
/// The flows the operator defined.
@ -338,9 +374,9 @@ impl Document {
}
let builtin = builtins();
for id in &self.disabled_builtins {
if !builtin.iter().any(|b| b.id == *id) {
if !builtin.iter().any(|b| b.id == *id) && !is_archetype_key(id) {
return Err(format!(
"disabled_builtins names {id}, which is no built-in flow"
"disabled_builtins names {id}, which is no built-in flow and no archetype flow key"
));
}
}
@ -1802,4 +1838,35 @@ mod tests {
assert!(f.validate_archetype().is_err(), "{key} {text}");
}
}
/// The keys that switch an archetype's flow off join `disabled_builtins`;
/// one that names nothing there is still accepted, one of no valid shape is not.
#[test]
fn archetype_flow_keys_are_accepted_in_the_disabled_list() {
let flow = archetype_flow_key("triage", "new-issues");
let member = archetype_member_key("triage", "new-issues", "alice");
assert_eq!(flow, "archetype:triage/new-issues");
assert_eq!(member, "archetype:triage/new-issues@alice");
let with = |keys: &[&str]| Document {
disabled_builtins: keys.iter().map(|k| (*k).to_owned()).collect(),
..Document::default()
};
assert_eq!(with(&[&flow, &member, "closed"]).check(), Ok(()));
assert_eq!(with(&["archetype:gone/flow"]).check(), Ok(()), "stale");
for bad in [
"archetype:triage",
"archetype:/x",
"archetype:a/",
"archetype:a/b@",
"archetype:a b/c",
"archetype:a/b/c",
"triage/new-issues",
"no-such-builtin",
] {
assert!(with(&[bad]).check().is_err(), "{bad}");
assert!(!is_archetype_key(bad), "{bad}");
}
// A stale key leaves the built-ins as they were.
assert_eq!(with(&[&flow]).active().0.len(), builtins().len());
}
}