an archetype carries its own notification-router flows for its agents
ArchetypeMeta gets flows, checked when the controller stores the record: every todo must go to the new members recipient, which the router expands to the agents of the archetype. The operator's flows document refuses members. The router reads the archetype records and each roster agent's archetype key, and its NATS grant gains read on both buckets.
This commit is contained in:
parent
ca339b7ba3
commit
1efbb72fdd
1 changed files with 883 additions and 29 deletions
|
|
@ -13,6 +13,7 @@ use std::collections::BTreeMap;
|
|||
|
||||
use swarm_wire::agent_inference::AgentInference;
|
||||
use swarm_wire::agent_limits::AgentCaps;
|
||||
use swarm_wire::notify_flows::Flow;
|
||||
|
||||
/// The KV bucket archetype sources are published into, keyed by source name.
|
||||
pub const BUCKET: &str = "agent-archetypes";
|
||||
|
|
@ -62,6 +63,11 @@ pub struct ArchetypeMeta {
|
|||
/// none.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub icon: Option<String>,
|
||||
/// The notification-router flows every agent of this archetype gets, next
|
||||
/// to the operator's document and the built-ins. Their todos go to
|
||||
/// `Recipients::Members`; the router runs them, and absent means none.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub flows: Vec<Flow>,
|
||||
}
|
||||
|
||||
/// Open the archetype bucket for writing, creating it if nothing has yet.
|
||||
|
|
@ -144,4 +150,60 @@ mod tests {
|
|||
assert!(wire.contains(r#""prompt":"be brief""#), "{wire}");
|
||||
assert_eq!(serde_json::from_str::<SourceRecord>(&wire).unwrap(), record);
|
||||
}
|
||||
|
||||
fn members_flow() -> Flow {
|
||||
use swarm_wire::notify_flows::{Edge, Node, NodeKind, Recipients, TodoSink};
|
||||
use swarm_wire::router_todo::Op;
|
||||
|
||||
Flow {
|
||||
id: "new-issues".to_owned(),
|
||||
name: "new issues".to_owned(),
|
||||
enabled: true,
|
||||
nodes: vec![
|
||||
Node {
|
||||
id: "src".to_owned(),
|
||||
kind: NodeKind::Source {
|
||||
events: vec!["issues".to_owned()],
|
||||
},
|
||||
},
|
||||
Node {
|
||||
id: "tell".to_owned(),
|
||||
kind: NodeKind::Todo(TodoSink {
|
||||
to: Recipients::Members,
|
||||
op: Op::Upsert,
|
||||
key: "forge:{{ repo }}#{{ number }}:new".to_owned(),
|
||||
prefix: false,
|
||||
text: "new issue {{ title }}".to_owned(),
|
||||
}),
|
||||
},
|
||||
],
|
||||
edges: vec![Edge {
|
||||
from: "src".to_owned(),
|
||||
to: "tell".to_owned(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
/// A record stored before `flows` existed reads as an archetype with none,
|
||||
/// and an archetype with none is stored without the key.
|
||||
#[test]
|
||||
fn a_record_without_flows_parses_and_encodes_without_them() {
|
||||
let old = r#"{"source":"s","archetypes":{"reviewer":{"description":"reads"}}}"#;
|
||||
let record: SourceRecord = serde_json::from_str(old).unwrap();
|
||||
assert!(record.archetypes["reviewer"].flows.is_empty());
|
||||
assert_eq!(serde_json::to_string(&record).unwrap(), old);
|
||||
}
|
||||
|
||||
/// An archetype's flows survive the bucket, `members` recipient included.
|
||||
#[test]
|
||||
fn flows_round_trip_with_the_members_recipient() {
|
||||
let meta = ArchetypeMeta {
|
||||
description: "triage".to_owned(),
|
||||
flows: vec![members_flow()],
|
||||
..ArchetypeMeta::default()
|
||||
};
|
||||
let wire = serde_json::to_string(&meta).unwrap();
|
||||
assert!(wire.contains(r#""to":"members""#), "{wire}");
|
||||
assert_eq!(serde_json::from_str::<ArchetypeMeta>(&wire).unwrap(), meta);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue